1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.packageinstaller.handheld;
18 
19 import static android.text.format.Formatter.formatFileSize;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.app.DialogFragment;
26 import android.app.usage.StorageStats;
27 import android.app.usage.StorageStatsManager;
28 import android.content.DialogInterface;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.UserInfo;
33 import android.os.Bundle;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.util.Log;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.widget.CheckBox;
41 import android.widget.TextView;
42 
43 import com.android.packageinstaller.R;
44 import com.android.packageinstaller.UninstallerActivity;
45 
46 import java.io.IOException;
47 import java.util.List;
48 
49 public class UninstallAlertDialogFragment extends DialogFragment implements
50         DialogInterface.OnClickListener {
51     private static final String LOG_TAG = UninstallAlertDialogFragment.class.getSimpleName();
52 
53     private @Nullable CheckBox mKeepData;
54 
55     /**
56      * Get number of bytes of the app data of the package.
57      *
58      * @param pkg The package that might have app data.
59      * @param user The user the package belongs to
60      *
61      * @return The number of bytes.
62      */
getAppDataSizeForUser(@onNull String pkg, @NonNull UserHandle user)63     private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
64         StorageStatsManager storageStatsManager =
65                 getContext().getSystemService(StorageStatsManager.class);
66         try {
67             StorageStats stats = storageStatsManager.queryStatsForPackage(
68                     getContext().getPackageManager().getApplicationInfo(pkg, 0).storageUuid,
69                     pkg, user);
70             return stats.getDataBytes();
71         } catch (PackageManager.NameNotFoundException | IOException e) {
72             Log.e(LOG_TAG, "Cannot determine amount of app data for " + pkg, e);
73         }
74 
75         return 0;
76     }
77 
78     /**
79      * Get number of bytes of the app data of the package.
80      *
81      * @param pkg The package that might have app data.
82      * @param user The user the package belongs to or {@code null} if files of all users should be
83      *             counted.
84      *
85      * @return The number of bytes.
86      */
getAppDataSize(@onNull String pkg, @Nullable UserHandle user)87     private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) {
88         UserManager userManager = getContext().getSystemService(UserManager.class);
89 
90         long appDataSize = 0;
91 
92         if (user == null) {
93             List<UserInfo> users = userManager.getUsers();
94 
95             int numUsers = users.size();
96             for (int i = 0; i < numUsers; i++) {
97                 appDataSize += getAppDataSizeForUser(pkg, UserHandle.of(users.get(i).id));
98             }
99         } else {
100             appDataSize = getAppDataSizeForUser(pkg, user);
101         }
102 
103         return appDataSize;
104     }
105 
106     @Override
onCreateDialog(Bundle savedInstanceState)107     public Dialog onCreateDialog(Bundle savedInstanceState) {
108         final PackageManager pm = getActivity().getPackageManager();
109         final UninstallerActivity.DialogInfo dialogInfo =
110                 ((UninstallerActivity) getActivity()).getDialogInfo();
111         final CharSequence appLabel = dialogInfo.appInfo.loadSafeLabel(pm);
112         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
113         StringBuilder messageBuilder = new StringBuilder();
114 
115         // If the Activity label differs from the App label, then make sure the user
116         // knows the Activity belongs to the App being uninstalled.
117         if (dialogInfo.activityInfo != null) {
118             final CharSequence activityLabel = dialogInfo.activityInfo.loadSafeLabel(pm);
119             if (!activityLabel.equals(appLabel)) {
120                 messageBuilder.append(
121                         getString(R.string.uninstall_activity_text, activityLabel));
122                 messageBuilder.append(" ").append(appLabel).append(".\n\n");
123             }
124         }
125 
126         final boolean isUpdate =
127                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
128         UserManager userManager = UserManager.get(getActivity());
129         if (isUpdate) {
130             if (isSingleUser(userManager)) {
131                 messageBuilder.append(getString(R.string.uninstall_update_text));
132             } else {
133                 messageBuilder.append(getString(R.string.uninstall_update_text_multiuser));
134             }
135         } else {
136             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
137                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
138             } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) {
139                 UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
140                 messageBuilder.append(
141                         getString(R.string.uninstall_application_text_user, userInfo.name));
142             } else {
143                 messageBuilder.append(getString(R.string.uninstall_application_text));
144             }
145         }
146 
147         dialogBuilder.setTitle(appLabel);
148         dialogBuilder.setPositiveButton(android.R.string.ok, this);
149         dialogBuilder.setNegativeButton(android.R.string.cancel, this);
150 
151         String pkg = dialogInfo.appInfo.packageName;
152 
153         boolean suggestToKeepAppData;
154         try {
155             PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0);
156 
157             suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData();
158         } catch (PackageManager.NameNotFoundException e) {
159             Log.e(LOG_TAG, "Cannot check hasFragileUserData for " + pkg, e);
160             suggestToKeepAppData = false;
161         }
162 
163         long appDataSize = 0;
164         if (suggestToKeepAppData) {
165             appDataSize = getAppDataSize(pkg, dialogInfo.allUsers ? null : dialogInfo.user);
166         }
167 
168         if (appDataSize == 0) {
169             dialogBuilder.setMessage(messageBuilder.toString());
170         } else {
171             LayoutInflater inflater = getContext().getSystemService(LayoutInflater.class);
172             ViewGroup content = (ViewGroup) inflater.inflate(R.layout.uninstall_content_view, null);
173 
174             ((TextView) content.requireViewById(R.id.message)).setText(messageBuilder.toString());
175             mKeepData = content.requireViewById(R.id.keepData);
176             mKeepData.setVisibility(View.VISIBLE);
177             mKeepData.setText(getString(R.string.uninstall_keep_data,
178                     formatFileSize(getContext(), appDataSize)));
179 
180             dialogBuilder.setView(content);
181         }
182 
183         return dialogBuilder.create();
184     }
185 
186     @Override
onClick(DialogInterface dialog, int which)187     public void onClick(DialogInterface dialog, int which) {
188         if (which == Dialog.BUTTON_POSITIVE) {
189             ((UninstallerActivity) getActivity()).startUninstallProgress(
190                     mKeepData != null && mKeepData.isChecked());
191         } else {
192             ((UninstallerActivity) getActivity()).dispatchAborted();
193         }
194     }
195 
196     @Override
onDismiss(DialogInterface dialog)197     public void onDismiss(DialogInterface dialog) {
198         super.onDismiss(dialog);
199         if (isAdded()) {
200             getActivity().finish();
201         }
202     }
203 
204     /**
205      * Returns whether there is only one user on this device, not including
206      * the system-only user.
207      */
isSingleUser(UserManager userManager)208     private boolean isSingleUser(UserManager userManager) {
209         final int userCount = userManager.getUserCount();
210         return userCount == 1
211                 || (UserManager.isSplitSystemUser() && userCount == 2);
212     }
213 }
214