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