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