• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.app.AppOpsManager.MODE_ALLOWED;
20 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
21 
22 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
23 
24 import android.Manifest;
25 import android.annotation.NonNull;
26 import android.annotation.StringRes;
27 import android.app.Activity;
28 import android.app.ActivityManager;
29 import android.app.ActivityThread;
30 import android.app.AppGlobals;
31 import android.app.AppOpsManager;
32 import android.app.DialogFragment;
33 import android.app.Fragment;
34 import android.app.FragmentTransaction;
35 import android.app.Notification;
36 import android.app.NotificationChannel;
37 import android.app.NotificationManager;
38 import android.app.PendingIntent;
39 import android.content.ComponentName;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.pm.ActivityInfo;
43 import android.content.pm.ApplicationInfo;
44 import android.content.pm.IPackageDeleteObserver2;
45 import android.content.pm.IPackageManager;
46 import android.content.pm.PackageInstaller;
47 import android.content.pm.PackageManager;
48 import android.content.pm.VersionedPackage;
49 import android.content.res.Configuration;
50 import android.net.Uri;
51 import android.os.Build;
52 import android.os.Bundle;
53 import android.os.IBinder;
54 import android.os.RemoteException;
55 import android.os.ServiceManager;
56 import android.os.UserHandle;
57 import android.os.UserManager;
58 import android.util.Log;
59 
60 import com.android.packageinstaller.handheld.ErrorDialogFragment;
61 import com.android.packageinstaller.handheld.UninstallAlertDialogFragment;
62 import com.android.packageinstaller.television.ErrorFragment;
63 import com.android.packageinstaller.television.UninstallAlertFragment;
64 import com.android.packageinstaller.television.UninstallAppProgress;
65 
66 import java.util.List;
67 
68 /*
69  * This activity presents UI to uninstall an application. Usually launched with intent
70  * Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute
71  * com.android.packageinstaller.PackageName set to the application package name
72  */
73 public class UninstallerActivity extends Activity {
74     private static final String TAG = "UninstallerActivity";
75 
76     private static final String UNINSTALLING_CHANNEL = "uninstalling";
77 
78     public static class DialogInfo {
79         public ApplicationInfo appInfo;
80         public ActivityInfo activityInfo;
81         public boolean allUsers;
82         public UserHandle user;
83         public IBinder callback;
84     }
85 
86     private String mPackageName;
87     private DialogInfo mDialogInfo;
88 
89     @Override
onCreate(Bundle icicle)90     public void onCreate(Bundle icicle) {
91         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
92 
93         // Never restore any state, esp. never create any fragments. The data in the fragment might
94         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
95         super.onCreate(null);
96 
97         try {
98             int callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken());
99 
100             String callingPackage = getPackageNameForUid(callingUid);
101             if (callingPackage == null) {
102                 Log.e(TAG, "Package not found for originating uid " + callingUid);
103                 setResult(Activity.RESULT_FIRST_USER);
104                 finish();
105                 return;
106             } else {
107                 AppOpsManager appOpsManager = (AppOpsManager) getSystemService(
108                         Context.APP_OPS_SERVICE);
109                 if (appOpsManager.noteOpNoThrow(
110                         AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
111                         != MODE_ALLOWED) {
112                     Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
113                     setResult(Activity.RESULT_FIRST_USER);
114                     finish();
115                     return;
116                 }
117             }
118 
119             if (getMaxTargetSdkVersionForUid(this, callingUid)
120                     >= Build.VERSION_CODES.P && AppGlobals.getPackageManager().checkUidPermission(
121                     Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid)
122                     != PackageManager.PERMISSION_GRANTED
123                     && AppGlobals.getPackageManager().checkUidPermission(
124                             Manifest.permission.DELETE_PACKAGES, callingUid)
125                             != PackageManager.PERMISSION_GRANTED) {
126                 Log.e(TAG, "Uid " + callingUid + " does not have "
127                         + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
128                         + Manifest.permission.DELETE_PACKAGES);
129 
130                 setResult(Activity.RESULT_FIRST_USER);
131                 finish();
132                 return;
133             }
134         } catch (RemoteException ex) {
135             // Cannot reach Package/ActivityManager. Aborting uninstall.
136             Log.e(TAG, "Could not determine the launching uid.");
137 
138             setResult(Activity.RESULT_FIRST_USER);
139             finish();
140             return;
141         }
142 
143         // Get intent information.
144         // We expect an intent with URI of the form package://<packageName>#<className>
145         // className is optional; if specified, it is the activity the user chose to uninstall
146         final Intent intent = getIntent();
147         final Uri packageUri = intent.getData();
148         if (packageUri == null) {
149             Log.e(TAG, "No package URI in intent");
150             showAppNotFound();
151             return;
152         }
153         mPackageName = packageUri.getEncodedSchemeSpecificPart();
154         if (mPackageName == null) {
155             Log.e(TAG, "Invalid package name in URI: " + packageUri);
156             showAppNotFound();
157             return;
158         }
159 
160         final IPackageManager pm = IPackageManager.Stub.asInterface(
161                 ServiceManager.getService("package"));
162 
163         mDialogInfo = new DialogInfo();
164 
165         mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
166         if (mDialogInfo.allUsers && !UserManager.get(this).isAdminUser()) {
167             Log.e(TAG, "Only admin user can request uninstall for all users");
168             showUserIsNotAllowed();
169             return;
170         }
171         mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER);
172         if (mDialogInfo.user == null) {
173             mDialogInfo.user = android.os.Process.myUserHandle();
174         } else {
175             UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
176             List<UserHandle> profiles = userManager.getUserProfiles();
177             if (!profiles.contains(mDialogInfo.user)) {
178                 Log.e(TAG, "User " + android.os.Process.myUserHandle() + " can't request uninstall "
179                         + "for user " + mDialogInfo.user);
180                 showUserIsNotAllowed();
181                 return;
182             }
183         }
184 
185         mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
186 
187         try {
188             mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName,
189                     PackageManager.MATCH_ANY_USER, mDialogInfo.user.getIdentifier());
190         } catch (RemoteException e) {
191             Log.e(TAG, "Unable to get packageName. Package manager is dead?");
192         }
193 
194         if (mDialogInfo.appInfo == null) {
195             Log.e(TAG, "Invalid packageName: " + mPackageName);
196             showAppNotFound();
197             return;
198         }
199 
200         // The class name may have been specified (e.g. when deleting an app from all apps)
201         final String className = packageUri.getFragment();
202         if (className != null) {
203             try {
204                 mDialogInfo.activityInfo = pm.getActivityInfo(
205                         new ComponentName(mPackageName, className), 0,
206                         mDialogInfo.user.getIdentifier());
207             } catch (RemoteException e) {
208                 Log.e(TAG, "Unable to get className. Package manager is dead?");
209                 // Continue as the ActivityInfo isn't critical.
210             }
211         }
212 
213         showConfirmationDialog();
214     }
215 
getDialogInfo()216     public DialogInfo getDialogInfo() {
217         return mDialogInfo;
218     }
219 
showConfirmationDialog()220     private void showConfirmationDialog() {
221         if (isTv()) {
222             showContentFragment(new UninstallAlertFragment(), 0, 0);
223         } else {
224             showDialogFragment(new UninstallAlertDialogFragment(), 0, 0);
225         }
226     }
227 
showAppNotFound()228     private void showAppNotFound() {
229         if (isTv()) {
230             showContentFragment(new ErrorFragment(), R.string.app_not_found_dlg_title,
231                     R.string.app_not_found_dlg_text);
232         } else {
233             showDialogFragment(new ErrorDialogFragment(), R.string.app_not_found_dlg_title,
234                     R.string.app_not_found_dlg_text);
235         }
236     }
237 
showUserIsNotAllowed()238     private void showUserIsNotAllowed() {
239         if (isTv()) {
240             showContentFragment(new ErrorFragment(),
241                     R.string.user_is_not_allowed_dlg_title, R.string.user_is_not_allowed_dlg_text);
242         } else {
243             showDialogFragment(new ErrorDialogFragment(), 0, R.string.user_is_not_allowed_dlg_text);
244         }
245     }
246 
showGenericError()247     private void showGenericError() {
248         if (isTv()) {
249             showContentFragment(new ErrorFragment(),
250                     R.string.generic_error_dlg_title, R.string.generic_error_dlg_text);
251         } else {
252             showDialogFragment(new ErrorDialogFragment(), 0, R.string.generic_error_dlg_text);
253         }
254     }
255 
isTv()256     private boolean isTv() {
257         return (getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
258                 == Configuration.UI_MODE_TYPE_TELEVISION;
259     }
260 
showContentFragment(@onNull Fragment fragment, @StringRes int title, @StringRes int text)261     private void showContentFragment(@NonNull Fragment fragment, @StringRes int title,
262             @StringRes int text) {
263         Bundle args = new Bundle();
264         args.putInt(ErrorFragment.TITLE, title);
265         args.putInt(ErrorFragment.TEXT, text);
266         fragment.setArguments(args);
267 
268         getFragmentManager().beginTransaction()
269                 .replace(android.R.id.content, fragment)
270                 .commit();
271     }
272 
showDialogFragment(@onNull DialogFragment fragment, @StringRes int title, @StringRes int text)273     private void showDialogFragment(@NonNull DialogFragment fragment,
274             @StringRes int title, @StringRes int text) {
275         FragmentTransaction ft = getFragmentManager().beginTransaction();
276         Fragment prev = getFragmentManager().findFragmentByTag("dialog");
277         if (prev != null) {
278             ft.remove(prev);
279         }
280 
281         Bundle args = new Bundle();
282         if (title != 0) {
283             args.putInt(ErrorDialogFragment.TITLE, title);
284         }
285         args.putInt(ErrorDialogFragment.TEXT, text);
286 
287         fragment.setArguments(args);
288         fragment.show(ft, "dialog");
289     }
290 
startUninstallProgress(boolean keepData)291     public void startUninstallProgress(boolean keepData) {
292         boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
293         CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager());
294 
295         if (isTv()) {
296             Intent newIntent = new Intent(Intent.ACTION_VIEW);
297             newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);
298             newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
299             newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
300             newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
301 
302             if (returnResult) {
303                 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
304                 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
305             }
306 
307             newIntent.setClass(this, UninstallAppProgress.class);
308             startActivity(newIntent);
309         } else if (returnResult || mDialogInfo.callback != null || getCallingActivity() != null) {
310             Intent newIntent = new Intent(this, UninstallUninstalling.class);
311 
312             newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);
313             newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
314             newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
315             newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
316             newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);
317             newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
318 
319             if (returnResult) {
320                 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
321             }
322 
323             if (returnResult || getCallingActivity() != null) {
324                 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
325             }
326 
327             startActivity(newIntent);
328         } else {
329             int uninstallId;
330             try {
331                 uninstallId = UninstallEventReceiver.getNewId(this);
332             } catch (EventResultPersister.OutOfIdsException e) {
333                 showGenericError();
334                 return;
335             }
336 
337             Intent broadcastIntent = new Intent(this, UninstallFinish.class);
338 
339             broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
340             broadcastIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
341             broadcastIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
342             broadcastIntent.putExtra(UninstallFinish.EXTRA_APP_LABEL, label);
343             broadcastIntent.putExtra(UninstallFinish.EXTRA_UNINSTALL_ID, uninstallId);
344 
345             PendingIntent pendingIntent =
346                     PendingIntent.getBroadcast(this, uninstallId, broadcastIntent,
347                             PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
348 
349             NotificationManager notificationManager = getSystemService(NotificationManager.class);
350             NotificationChannel uninstallingChannel = new NotificationChannel(UNINSTALLING_CHANNEL,
351                     getString(R.string.uninstalling_notification_channel),
352                     NotificationManager.IMPORTANCE_MIN);
353             notificationManager.createNotificationChannel(uninstallingChannel);
354 
355             Notification uninstallingNotification =
356                     (new Notification.Builder(this, UNINSTALLING_CHANNEL))
357                     .setSmallIcon(R.drawable.ic_remove).setProgress(0, 1, true)
358                     .setContentTitle(getString(R.string.uninstalling_app, label)).setOngoing(true)
359                     .build();
360 
361             notificationManager.notify(uninstallId, uninstallingNotification);
362 
363             try {
364                 Log.i(TAG, "Uninstalling extras=" + broadcastIntent.getExtras());
365 
366                 int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
367                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
368 
369                 ActivityThread.getPackageManager().getPackageInstaller().uninstall(
370                         new VersionedPackage(mDialogInfo.appInfo.packageName,
371                                 PackageManager.VERSION_CODE_HIGHEST),
372                         getPackageName(), flags, pendingIntent.getIntentSender(),
373                         mDialogInfo.user.getIdentifier());
374             } catch (Exception e) {
375                 notificationManager.cancel(uninstallId);
376 
377                 Log.e(TAG, "Cannot start uninstall", e);
378                 showGenericError();
379             }
380         }
381     }
382 
dispatchAborted()383     public void dispatchAborted() {
384         if (mDialogInfo != null && mDialogInfo.callback != null) {
385             final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub.asInterface(
386                     mDialogInfo.callback);
387             try {
388                 observer.onPackageDeleted(mPackageName,
389                         PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
390             } catch (RemoteException ignored) {
391             }
392         }
393     }
394 
getPackageNameForUid(int sourceUid)395     private String getPackageNameForUid(int sourceUid) {
396         String[] packagesForUid = getPackageManager().getPackagesForUid(sourceUid);
397         if (packagesForUid == null) {
398             return null;
399         }
400         return packagesForUid[0];
401     }
402 }
403