The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 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 | |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 19 | import android.app.Activity; |
| 20 | import android.app.AlertDialog; |
| 21 | import android.app.Dialog; |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 22 | import android.app.DialogFragment; |
| 23 | import android.app.Fragment; |
| 24 | import android.app.FragmentTransaction; |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 25 | import android.content.ComponentName; |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 26 | import android.content.DialogInterface; |
| 27 | import android.content.Intent; |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 28 | import android.content.pm.ActivityInfo; |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 29 | import android.content.pm.ApplicationInfo; |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 30 | import android.content.pm.IPackageDeleteObserver2; |
Kenny Guy | 596ce64 | 2014-06-03 17:50:38 +0100 | [diff] [blame] | 31 | import android.content.pm.IPackageManager; |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 32 | import android.content.pm.PackageInstaller; |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 33 | import android.content.pm.PackageManager; |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 34 | import android.content.pm.UserInfo; |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 35 | import android.net.Uri; |
| 36 | import android.os.Bundle; |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 37 | import android.os.IBinder; |
Kenny Guy | 596ce64 | 2014-06-03 17:50:38 +0100 | [diff] [blame] | 38 | import android.os.RemoteException; |
| 39 | import android.os.ServiceManager; |
| 40 | import android.os.UserHandle; |
Dianne Hackborn | 8124c24 | 2012-10-31 17:16:35 -0700 | [diff] [blame] | 41 | import android.os.UserManager; |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 42 | import android.util.Log; |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 43 | |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 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 | */ |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 49 | public class UninstallerActivity extends Activity { |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 50 | private static final String TAG = "UninstallerActivity"; |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 51 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 52 | public static class UninstallAlertDialogFragment extends DialogFragment implements |
| 53 | DialogInterface.OnClickListener { |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 54 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 55 | @Override |
| 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); |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 60 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 61 | AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); |
| 62 | StringBuilder messageBuilder = new StringBuilder(); |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 63 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 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 | } |
Suchi Amalapurapu | d9b773b | 2009-09-01 14:35:25 -0700 | [diff] [blame] | 74 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 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 |
| 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 |
| 110 | public void onDismiss(DialogInterface dialog) { |
| 111 | super.onDismiss(dialog); |
zhuqk | 6f032ac | 2015-09-09 11:01:30 +0800 | [diff] [blame] | 112 | Activity activity = getActivity(); |
| 113 | if(activity != null) |
| 114 | activity.finish(); |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 115 | } |
| 116 | } |
| 117 | |
| 118 | public static class AppNotFoundDialogFragment extends DialogFragment { |
| 119 | |
| 120 | @Override |
| 121 | public Dialog onCreateDialog(Bundle savedInstanceState) { |
| 122 | return new AlertDialog.Builder(getActivity()) |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 123 | .setTitle(R.string.app_not_found_dlg_title) |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 124 | .setMessage(R.string.app_not_found_dlg_text) |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 125 | .setNeutralButton(android.R.string.ok, null) |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 126 | .create(); |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 127 | } |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 128 | |
| 129 | @Override |
| 130 | public void onDismiss(DialogInterface dialog) { |
| 131 | super.onDismiss(dialog); |
| 132 | ((UninstallerActivity) getActivity()).dispatchAborted(); |
| 133 | getActivity().setResult(Activity.RESULT_FIRST_USER); |
| 134 | getActivity().finish(); |
| 135 | } |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 136 | } |
| 137 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 138 | static class DialogInfo { |
| 139 | ApplicationInfo appInfo; |
| 140 | ActivityInfo activityInfo; |
| 141 | boolean allUsers; |
| 142 | UserHandle user; |
| 143 | IBinder callback; |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 144 | } |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 145 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 146 | private DialogInfo mDialogInfo; |
| 147 | |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 148 | @Override |
| 149 | public void onCreate(Bundle icicle) { |
| 150 | super.onCreate(icicle); |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 151 | // Get intent information. |
| 152 | // We expect an intent with URI of the form package://<packageName>#<className> |
| 153 | // className is optional; if specified, it is the activity the user chose to uninstall |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 154 | final Intent intent = getIntent(); |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 155 | final Uri packageUri = intent.getData(); |
| 156 | if (packageUri == null) { |
| 157 | Log.e(TAG, "No package URI in intent"); |
| 158 | showAppNotFound(); |
| 159 | return; |
| 160 | } |
| 161 | final String packageName = packageUri.getEncodedSchemeSpecificPart(); |
| 162 | if (packageName == null) { |
| 163 | Log.e(TAG, "Invalid package name in URI: " + packageUri); |
| 164 | showAppNotFound(); |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 165 | return; |
| 166 | } |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 167 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 168 | final IPackageManager pm = IPackageManager.Stub.asInterface( |
| 169 | ServiceManager.getService("package")); |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 170 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 171 | mDialogInfo = new DialogInfo(); |
| 172 | |
| 173 | mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER); |
| 174 | if (mDialogInfo.user == null) { |
| 175 | mDialogInfo.user = android.os.Process.myUserHandle(); |
Kenny Guy | 596ce64 | 2014-06-03 17:50:38 +0100 | [diff] [blame] | 176 | } |
Kenny Guy | 596ce64 | 2014-06-03 17:50:38 +0100 | [diff] [blame] | 177 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 178 | mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); |
| 179 | mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK); |
| 180 | |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 181 | try { |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 182 | mDialogInfo.appInfo = pm.getApplicationInfo(packageName, |
| 183 | PackageManager.GET_UNINSTALLED_PACKAGES, mDialogInfo.user.getIdentifier()); |
Kenny Guy | 596ce64 | 2014-06-03 17:50:38 +0100 | [diff] [blame] | 184 | } catch (RemoteException e) { |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 185 | Log.e(TAG, "Unable to get packageName. Package manager is dead?"); |
| 186 | } |
| 187 | |
| 188 | if (mDialogInfo.appInfo == null) { |
| 189 | Log.e(TAG, "Invalid packageName: " + packageName); |
| 190 | showAppNotFound(); |
| 191 | return; |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 192 | } |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 193 | |
| 194 | // The class name may have been specified (e.g. when deleting an app from all apps) |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 195 | final String className = packageUri.getFragment(); |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 196 | if (className != null) { |
| 197 | try { |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 198 | mDialogInfo.activityInfo = pm.getActivityInfo( |
| 199 | new ComponentName(packageName, className), 0, |
| 200 | mDialogInfo.user.getIdentifier()); |
Kenny Guy | 596ce64 | 2014-06-03 17:50:38 +0100 | [diff] [blame] | 201 | } catch (RemoteException e) { |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 202 | Log.e(TAG, "Unable to get className. Package manager is dead?"); |
| 203 | // Continue as the ActivityInfo isn't critical. |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 204 | } |
| 205 | } |
| 206 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 207 | showConfirmationDialog(); |
| 208 | } |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 209 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 210 | private void showConfirmationDialog() { |
| 211 | showDialogFragment(new UninstallAlertDialogFragment()); |
| 212 | } |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 213 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 214 | private void showAppNotFound() { |
| 215 | showDialogFragment(new AppNotFoundDialogFragment()); |
| 216 | } |
Patrick Dubroy | 362660b | 2010-09-02 18:33:09 -0700 | [diff] [blame] | 217 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 218 | private void showDialogFragment(DialogFragment fragment) { |
| 219 | FragmentTransaction ft = getFragmentManager().beginTransaction(); |
| 220 | Fragment prev = getFragmentManager().findFragmentByTag("dialog"); |
| 221 | if (prev != null) { |
| 222 | ft.remove(prev); |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 223 | } |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 224 | fragment.show(ft, "dialog"); |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 225 | } |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 226 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 227 | void startUninstallProgress() { |
| 228 | Intent newIntent = new Intent(Intent.ACTION_VIEW); |
| 229 | newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user); |
| 230 | newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers); |
| 231 | newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback); |
| 232 | newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo); |
| 233 | if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { |
| 234 | newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); |
| 235 | newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 236 | } |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 237 | newIntent.setClass(this, UninstallAppProgress.class); |
| 238 | startActivity(newIntent); |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 239 | } |
Suchi Amalapurapu | 7672c99 | 2009-07-28 17:41:25 -0700 | [diff] [blame] | 240 | |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 241 | void dispatchAborted() { |
| 242 | if (mDialogInfo != null && mDialogInfo.callback != null) { |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 243 | final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub.asInterface( |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 244 | mDialogInfo.callback); |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 245 | try { |
Adam Lesinski | 3e3e112 | 2014-08-29 11:30:47 -0700 | [diff] [blame] | 246 | observer.onPackageDeleted(mDialogInfo.appInfo.packageName, |
Jeff Sharkey | 3aeb505 | 2014-08-07 17:40:20 -0700 | [diff] [blame] | 247 | PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"); |
| 248 | } catch (RemoteException ignored) { |
| 249 | } |
| 250 | } |
Suchi Amalapurapu | 7672c99 | 2009-07-28 17:41:25 -0700 | [diff] [blame] | 251 | } |
The Android Open Source Project | 91ec61c | 2009-03-03 19:32:31 -0800 | [diff] [blame] | 252 | } |