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