1 /* 2 ** 3 ** Copyright 2007, The Android Open Source Project 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 package com.android.packageinstaller; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.app.DialogFragment; 23 import android.app.Fragment; 24 import android.app.FragmentTransaction; 25 import android.content.ComponentName; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.pm.ActivityInfo; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.IPackageDeleteObserver2; 31 import android.content.pm.IPackageManager; 32 import android.content.pm.PackageInstaller; 33 import android.content.pm.PackageManager; 34 import android.content.pm.UserInfo; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.os.RemoteException; 39 import android.os.ServiceManager; 40 import android.os.UserHandle; 41 import android.os.UserManager; 42 import android.util.Log; 43 44 /* 45 * This activity presents UI to uninstall an application. Usually launched with intent 46 * Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute 47 * com.android.packageinstaller.PackageName set to the application package name 48 */ 49 public class UninstallerActivity extends Activity { 50 private static final String TAG = "UninstallerActivity"; 51 52 public static class UninstallAlertDialogFragment extends DialogFragment implements 53 DialogInterface.OnClickListener { 54 55 @Override onCreateDialog(Bundle savedInstanceState)56 public Dialog onCreateDialog(Bundle savedInstanceState) { 57 final PackageManager pm = getActivity().getPackageManager(); 58 final DialogInfo dialogInfo = ((UninstallerActivity) getActivity()).mDialogInfo; 59 final CharSequence appLabel = dialogInfo.appInfo.loadLabel(pm); 60 61 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); 62 StringBuilder messageBuilder = new StringBuilder(); 63 64 // If the Activity label differs from the App label, then make sure the user 65 // knows the Activity belongs to the App being uninstalled. 66 if (dialogInfo.activityInfo != null) { 67 final CharSequence activityLabel = dialogInfo.activityInfo.loadLabel(pm); 68 if (!activityLabel.equals(appLabel)) { 69 messageBuilder.append( 70 getString(R.string.uninstall_activity_text, activityLabel)); 71 messageBuilder.append(" ").append(appLabel).append(".\n\n"); 72 } 73 } 74 75 final boolean isUpdate = 76 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); 77 UserManager userManager = UserManager.get(getActivity()); 78 if (isUpdate) { 79 if (isSingleUser(userManager)) { 80 messageBuilder.append(getString(R.string.uninstall_update_text)); 81 } else { 82 messageBuilder.append(getString(R.string.uninstall_update_text_multiuser)); 83 } 84 } else { 85 if (dialogInfo.allUsers && !isSingleUser(userManager)) { 86 messageBuilder.append(getString(R.string.uninstall_application_text_all_users)); 87 } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) { 88 UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier()); 89 messageBuilder.append( 90 getString(R.string.uninstall_application_text_user, userInfo.name)); 91 } else { 92 messageBuilder.append(getString(R.string.uninstall_application_text)); 93 } 94 } 95 96 dialogBuilder.setTitle(appLabel); 97 dialogBuilder.setIcon(dialogInfo.appInfo.loadIcon(pm)); 98 dialogBuilder.setPositiveButton(android.R.string.ok, this); 99 dialogBuilder.setNegativeButton(android.R.string.cancel, this); 100 dialogBuilder.setMessage(messageBuilder.toString()); 101 return dialogBuilder.create(); 102 } 103 104 @Override onClick(DialogInterface dialog, int which)105 public void onClick(DialogInterface dialog, int which) { 106 if (which == Dialog.BUTTON_POSITIVE) { 107 ((UninstallerActivity) getActivity()).startUninstallProgress(); 108 } else { 109 ((UninstallerActivity) getActivity()).dispatchAborted(); 110 } 111 } 112 113 @Override onDismiss(DialogInterface dialog)114 public void onDismiss(DialogInterface dialog) { 115 super.onDismiss(dialog); 116 if (isAdded()) { 117 getActivity().finish(); 118 } 119 } 120 121 /** 122 * Returns whether there is only one user on this device, not including 123 * the system-only user. 124 */ isSingleUser(UserManager userManager)125 private boolean isSingleUser(UserManager userManager) { 126 final int userCount = userManager.getUserCount(); 127 return userCount == 1 128 || (UserManager.isSplitSystemUser() && userCount == 2); 129 } 130 } 131 132 public static class AppNotFoundDialogFragment extends DialogFragment { 133 134 @Override onCreateDialog(Bundle savedInstanceState)135 public Dialog onCreateDialog(Bundle savedInstanceState) { 136 return new AlertDialog.Builder(getActivity()) 137 .setTitle(R.string.app_not_found_dlg_title) 138 .setMessage(R.string.app_not_found_dlg_text) 139 .setNeutralButton(android.R.string.ok, null) 140 .create(); 141 } 142 143 @Override onDismiss(DialogInterface dialog)144 public void onDismiss(DialogInterface dialog) { 145 super.onDismiss(dialog); 146 if (isAdded()) { 147 ((UninstallerActivity) getActivity()).dispatchAborted(); 148 getActivity().setResult(Activity.RESULT_FIRST_USER); 149 getActivity().finish(); 150 } 151 } 152 } 153 154 static class DialogInfo { 155 ApplicationInfo appInfo; 156 ActivityInfo activityInfo; 157 boolean allUsers; 158 UserHandle user; 159 IBinder callback; 160 } 161 162 private String mPackageName; 163 private DialogInfo mDialogInfo; 164 165 @Override onCreate(Bundle icicle)166 public void onCreate(Bundle icicle) { 167 super.onCreate(icicle); 168 // Get intent information. 169 // We expect an intent with URI of the form package://<packageName>#<className> 170 // className is optional; if specified, it is the activity the user chose to uninstall 171 final Intent intent = getIntent(); 172 final Uri packageUri = intent.getData(); 173 if (packageUri == null) { 174 Log.e(TAG, "No package URI in intent"); 175 showAppNotFound(); 176 return; 177 } 178 mPackageName = packageUri.getEncodedSchemeSpecificPart(); 179 if (mPackageName == null) { 180 Log.e(TAG, "Invalid package name in URI: " + packageUri); 181 showAppNotFound(); 182 return; 183 } 184 185 final IPackageManager pm = IPackageManager.Stub.asInterface( 186 ServiceManager.getService("package")); 187 188 mDialogInfo = new DialogInfo(); 189 190 mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER); 191 if (mDialogInfo.user == null) { 192 mDialogInfo.user = android.os.Process.myUserHandle(); 193 } 194 195 mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 196 mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK); 197 198 try { 199 mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName, 200 PackageManager.GET_UNINSTALLED_PACKAGES, mDialogInfo.user.getIdentifier()); 201 } catch (RemoteException e) { 202 Log.e(TAG, "Unable to get packageName. Package manager is dead?"); 203 } 204 205 if (mDialogInfo.appInfo == null) { 206 Log.e(TAG, "Invalid packageName: " + mPackageName); 207 showAppNotFound(); 208 return; 209 } 210 211 // The class name may have been specified (e.g. when deleting an app from all apps) 212 final String className = packageUri.getFragment(); 213 if (className != null) { 214 try { 215 mDialogInfo.activityInfo = pm.getActivityInfo( 216 new ComponentName(mPackageName, className), 0, 217 mDialogInfo.user.getIdentifier()); 218 } catch (RemoteException e) { 219 Log.e(TAG, "Unable to get className. Package manager is dead?"); 220 // Continue as the ActivityInfo isn't critical. 221 } 222 } 223 224 showConfirmationDialog(); 225 } 226 showConfirmationDialog()227 private void showConfirmationDialog() { 228 showDialogFragment(new UninstallAlertDialogFragment()); 229 } 230 showAppNotFound()231 private void showAppNotFound() { 232 showDialogFragment(new AppNotFoundDialogFragment()); 233 } 234 showDialogFragment(DialogFragment fragment)235 private void showDialogFragment(DialogFragment fragment) { 236 FragmentTransaction ft = getFragmentManager().beginTransaction(); 237 Fragment prev = getFragmentManager().findFragmentByTag("dialog"); 238 if (prev != null) { 239 ft.remove(prev); 240 } 241 fragment.show(ft, "dialog"); 242 } 243 startUninstallProgress()244 void startUninstallProgress() { 245 Intent newIntent = new Intent(Intent.ACTION_VIEW); 246 newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user); 247 newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers); 248 newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback); 249 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo); 250 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 251 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 252 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 253 } 254 newIntent.setClass(this, UninstallAppProgress.class); 255 startActivity(newIntent); 256 } 257 dispatchAborted()258 void dispatchAborted() { 259 if (mDialogInfo != null && mDialogInfo.callback != null) { 260 final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub.asInterface( 261 mDialogInfo.callback); 262 try { 263 observer.onPackageDeleted(mPackageName, 264 PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"); 265 } catch (RemoteException ignored) { 266 } 267 } 268 } 269 } 270