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