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