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 if (isUpdate) { 78 messageBuilder.append(getString(R.string.uninstall_update_text)); 79 } else { 80 UserManager userManager = UserManager.get(getActivity()); 81 if (dialogInfo.allUsers && userManager.getUserCount() >= 2) { 82 messageBuilder.append(getString(R.string.uninstall_application_text_all_users)); 83 } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) { 84 UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier()); 85 messageBuilder.append( 86 getString(R.string.uninstall_application_text_user, userInfo.name)); 87 } else { 88 messageBuilder.append(getString(R.string.uninstall_application_text)); 89 } 90 } 91 92 dialogBuilder.setTitle(appLabel); 93 dialogBuilder.setIcon(dialogInfo.appInfo.loadIcon(pm)); 94 dialogBuilder.setPositiveButton(android.R.string.ok, this); 95 dialogBuilder.setNegativeButton(android.R.string.cancel, this); 96 dialogBuilder.setMessage(messageBuilder.toString()); 97 return dialogBuilder.create(); 98 } 99 100 @Override onClick(DialogInterface dialog, int which)101 public void onClick(DialogInterface dialog, int which) { 102 if (which == Dialog.BUTTON_POSITIVE) { 103 ((UninstallerActivity) getActivity()).startUninstallProgress(); 104 } else { 105 ((UninstallerActivity) getActivity()).dispatchAborted(); 106 } 107 } 108 109 @Override onDismiss(DialogInterface dialog)110 public void onDismiss(DialogInterface dialog) { 111 super.onDismiss(dialog); 112 getActivity().finish(); 113 } 114 } 115 116 public static class AppNotFoundDialogFragment extends DialogFragment { 117 118 @Override onCreateDialog(Bundle savedInstanceState)119 public Dialog onCreateDialog(Bundle savedInstanceState) { 120 return new AlertDialog.Builder(getActivity()) 121 .setTitle(R.string.app_not_found_dlg_title) 122 .setMessage(R.string.app_not_found_dlg_text) 123 .setNeutralButton(android.R.string.ok, null) 124 .create(); 125 } 126 127 @Override onDismiss(DialogInterface dialog)128 public void onDismiss(DialogInterface dialog) { 129 super.onDismiss(dialog); 130 ((UninstallerActivity) getActivity()).dispatchAborted(); 131 getActivity().setResult(Activity.RESULT_FIRST_USER); 132 getActivity().finish(); 133 } 134 } 135 136 static class DialogInfo { 137 ApplicationInfo appInfo; 138 ActivityInfo activityInfo; 139 boolean allUsers; 140 UserHandle user; 141 IBinder callback; 142 } 143 144 private DialogInfo mDialogInfo; 145 146 @Override onCreate(Bundle icicle)147 public void onCreate(Bundle icicle) { 148 super.onCreate(icicle); 149 // Get intent information. 150 // We expect an intent with URI of the form package://<packageName>#<className> 151 // className is optional; if specified, it is the activity the user chose to uninstall 152 final Intent intent = getIntent(); 153 final Uri packageUri = intent.getData(); 154 if (packageUri == null) { 155 Log.e(TAG, "No package URI in intent"); 156 showAppNotFound(); 157 return; 158 } 159 final String packageName = packageUri.getEncodedSchemeSpecificPart(); 160 if (packageName == null) { 161 Log.e(TAG, "Invalid package name in URI: " + packageUri); 162 showAppNotFound(); 163 return; 164 } 165 166 final IPackageManager pm = IPackageManager.Stub.asInterface( 167 ServiceManager.getService("package")); 168 169 mDialogInfo = new DialogInfo(); 170 171 mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER); 172 if (mDialogInfo.user == null) { 173 mDialogInfo.user = android.os.Process.myUserHandle(); 174 } 175 176 mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 177 mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK); 178 179 try { 180 mDialogInfo.appInfo = pm.getApplicationInfo(packageName, 181 PackageManager.GET_UNINSTALLED_PACKAGES, mDialogInfo.user.getIdentifier()); 182 } catch (RemoteException e) { 183 Log.e(TAG, "Unable to get packageName. Package manager is dead?"); 184 } 185 186 if (mDialogInfo.appInfo == null) { 187 Log.e(TAG, "Invalid packageName: " + packageName); 188 showAppNotFound(); 189 return; 190 } 191 192 // The class name may have been specified (e.g. when deleting an app from all apps) 193 final String className = packageUri.getFragment(); 194 if (className != null) { 195 try { 196 mDialogInfo.activityInfo = pm.getActivityInfo( 197 new ComponentName(packageName, className), 0, 198 mDialogInfo.user.getIdentifier()); 199 } catch (RemoteException e) { 200 Log.e(TAG, "Unable to get className. Package manager is dead?"); 201 // Continue as the ActivityInfo isn't critical. 202 } 203 } 204 205 showConfirmationDialog(); 206 } 207 showConfirmationDialog()208 private void showConfirmationDialog() { 209 showDialogFragment(new UninstallAlertDialogFragment()); 210 } 211 showAppNotFound()212 private void showAppNotFound() { 213 showDialogFragment(new AppNotFoundDialogFragment()); 214 } 215 showDialogFragment(DialogFragment fragment)216 private void showDialogFragment(DialogFragment fragment) { 217 FragmentTransaction ft = getFragmentManager().beginTransaction(); 218 Fragment prev = getFragmentManager().findFragmentByTag("dialog"); 219 if (prev != null) { 220 ft.remove(prev); 221 } 222 fragment.show(ft, "dialog"); 223 } 224 startUninstallProgress()225 void startUninstallProgress() { 226 Intent newIntent = new Intent(Intent.ACTION_VIEW); 227 newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user); 228 newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers); 229 newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback); 230 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo); 231 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 232 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 233 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 234 } 235 newIntent.setClass(this, UninstallAppProgress.class); 236 startActivity(newIntent); 237 } 238 dispatchAborted()239 void dispatchAborted() { 240 if (mDialogInfo != null && mDialogInfo.callback != null) { 241 final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub.asInterface( 242 mDialogInfo.callback); 243 try { 244 observer.onPackageDeleted(mDialogInfo.appInfo.packageName, 245 PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"); 246 } catch (RemoteException ignored) { 247 } 248 } 249 } 250 } 251