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.content.pm.Flags.usePiaV2;
21 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
22 
23 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
24 
25 import android.Manifest;
26 import android.app.Activity;
27 import android.app.AppOpsManager;
28 import android.app.DialogFragment;
29 import android.app.Fragment;
30 import android.app.FragmentTransaction;
31 import android.app.Notification;
32 import android.app.NotificationChannel;
33 import android.app.NotificationManager;
34 import android.app.PendingIntent;
35 import android.content.ComponentName;
36 import android.content.Intent;
37 import android.content.pm.ActivityInfo;
38 import android.content.pm.ApplicationInfo;
39 import android.content.pm.PackageInstaller;
40 import android.content.pm.PackageManager;
41 import android.content.pm.VersionedPackage;
42 import android.content.res.Configuration;
43 import android.net.Uri;
44 import android.os.Build;
45 import android.os.Bundle;
46 import android.os.Process;
47 import android.os.UserHandle;
48 import android.os.UserManager;
49 import android.util.Log;
50 import androidx.annotation.NonNull;
51 import androidx.annotation.StringRes;
52 import com.android.packageinstaller.handheld.ErrorDialogFragment;
53 import com.android.packageinstaller.handheld.UninstallAlertDialogFragment;
54 import com.android.packageinstaller.television.ErrorFragment;
55 import com.android.packageinstaller.television.UninstallAlertFragment;
56 import com.android.packageinstaller.television.UninstallAppProgress;
57 import com.android.packageinstaller.common.EventResultPersister;
58 import com.android.packageinstaller.common.UninstallEventReceiver;
59 import com.android.packageinstaller.v2.ui.UninstallLaunch;
60 
61 import java.util.List;
62 
63 /*
64  * This activity presents UI to uninstall an application. Usually launched with intent
65  * Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute
66  * com.android.packageinstaller.PackageName set to the application package name
67  */
68 public class UninstallerActivity extends Activity {
69     private static final String TAG = "UninstallerActivity";
70 
71     private static final String UNINSTALLING_CHANNEL = "uninstalling";
72     private boolean mIsClonedApp;
73 
74     public static class DialogInfo {
75         public ApplicationInfo appInfo;
76         public ActivityInfo activityInfo;
77         public boolean allUsers;
78         public UserHandle user;
79         public PackageManager.UninstallCompleteCallback callback;
80         public int deleteFlags;
81     }
82 
83     private String mPackageName;
84     private DialogInfo mDialogInfo;
85 
86     @Override
onCreate(Bundle icicle)87     public void onCreate(Bundle icicle) {
88         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
89 
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         // TODO(b/318521110) Enable PIA v2 for archive dialog.
95         if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) {
96             Log.i(TAG, "Using Pia V2");
97 
98             boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
99             Intent piaV2 = new Intent(getIntent());
100             piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid());
101             piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_ACTIVITY_NAME, getCallingActivity());
102             if (returnResult) {
103                 piaV2.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
104             }
105             piaV2.setClass(this, UninstallLaunch.class);
106             startActivity(piaV2);
107             finish();
108             return;
109         }
110 
111         int callingUid = getLaunchedFromUid();
112         if (callingUid == Process.INVALID_UID) {
113             // Cannot reach Package/ActivityManager. Aborting uninstall.
114             Log.e(TAG, "Could not determine the launching uid.");
115 
116             setResult(Activity.RESULT_FIRST_USER);
117             finish();
118             return;
119         }
120 
121         String callingPackage = getPackageNameForUid(callingUid);
122         if (callingPackage == null) {
123             Log.e(TAG, "Package not found for originating uid " + callingUid);
124             setResult(Activity.RESULT_FIRST_USER);
125             finish();
126             return;
127         } else {
128             AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
129             if (appOpsManager.noteOpNoThrow(
130                     AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
131                     != MODE_ALLOWED) {
132                 Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
133                 setResult(Activity.RESULT_FIRST_USER);
134                 finish();
135                 return;
136             }
137         }
138 
139         if (getMaxTargetSdkVersionForUid(this, callingUid) >= Build.VERSION_CODES.P
140                 && getBaseContext().checkPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
141                 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED
142                 && getBaseContext().checkPermission(Manifest.permission.DELETE_PACKAGES,
143                 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED) {
144             Log.e(TAG, "Uid " + callingUid + " does not have "
145                     + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
146                     + Manifest.permission.DELETE_PACKAGES);
147 
148             setResult(Activity.RESULT_FIRST_USER);
149             finish();
150             return;
151         }
152 
153         // Get intent information.
154         // We expect an intent with URI of the form package://<packageName>#<className>
155         // className is optional; if specified, it is the activity the user chose to uninstall
156         final Intent intent = getIntent();
157         final Uri packageUri = intent.getData();
158         if (packageUri == null) {
159             Log.e(TAG, "No package URI in intent");
160             showAppNotFound();
161             return;
162         }
163         mPackageName = packageUri.getEncodedSchemeSpecificPart();
164         if (mPackageName == null) {
165             Log.e(TAG, "Invalid package name in URI: " + packageUri);
166             showAppNotFound();
167             return;
168         }
169 
170         PackageManager pm = getPackageManager();
171         UserManager userManager = getBaseContext().getSystemService(UserManager.class);
172 
173         mDialogInfo = new DialogInfo();
174 
175         mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
176         if (mDialogInfo.allUsers && !userManager.isAdminUser()) {
177             Log.e(TAG, "Only admin user can request uninstall for all users");
178             showUserIsNotAllowed();
179             return;
180         }
181         mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER);
182         if (mDialogInfo.user == null) {
183             mDialogInfo.user = Process.myUserHandle();
184         } else {
185             List<UserHandle> profiles = userManager.getUserProfiles();
186             if (!profiles.contains(mDialogInfo.user)) {
187                 Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
188                         + "for user " + mDialogInfo.user);
189                 showUserIsNotAllowed();
190                 return;
191             }
192         }
193 
194         mDialogInfo.callback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
195                                             PackageManager.UninstallCompleteCallback.class);
196 
197         try {
198             mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName,
199                     PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER
200                             | PackageManager.MATCH_ARCHIVED_PACKAGES));
201         } catch (PackageManager.NameNotFoundException 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),
217                         PackageManager.ComponentInfoFlags.of(0));
218             } catch (PackageManager.NameNotFoundException e) {
219                 Log.e(TAG, "Unable to get className. Package manager is dead?");
220                 // Continue as the ActivityInfo isn't critical.
221             }
222         }
223         parseDeleteFlags(intent);
224 
225         showConfirmationDialog();
226     }
227 
isArchiveDialog(Intent intent)228     private boolean isArchiveDialog(Intent intent) {
229         return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0)
230                 & PackageManager.DELETE_ARCHIVE) != 0;
231     }
232 
233     /**
234      * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent}
235      * to archive an app if requested.
236      *
237      * Do not parse any flags because developers might pass here any flags which might cause
238      * unintended behaviour.
239      * For more context {@link com.android.server.pm.PackageArchiver#requestArchive}.
240      */
parseDeleteFlags(Intent intent)241     private void parseDeleteFlags(Intent intent) {
242         int deleteFlags = intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0);
243         int archive = deleteFlags & PackageManager.DELETE_ARCHIVE;
244         int keepData = deleteFlags & PackageManager.DELETE_KEEP_DATA;
245         mDialogInfo.deleteFlags = archive | keepData;
246     }
247 
getDialogInfo()248     public DialogInfo getDialogInfo() {
249         return mDialogInfo;
250     }
251 
showConfirmationDialog()252     private void showConfirmationDialog() {
253         if (isTv()) {
254             showContentFragment(new UninstallAlertFragment(), 0, 0);
255         } else {
256             showDialogFragment(new UninstallAlertDialogFragment(), 0, 0);
257         }
258     }
259 
showAppNotFound()260     private void showAppNotFound() {
261         if (isTv()) {
262             showContentFragment(new ErrorFragment(), R.string.app_not_found_dlg_title,
263                     R.string.app_not_found_dlg_text);
264         } else {
265             showDialogFragment(new ErrorDialogFragment(), R.string.app_not_found_dlg_title,
266                     R.string.app_not_found_dlg_text);
267         }
268     }
269 
showUserIsNotAllowed()270     private void showUserIsNotAllowed() {
271         if (isTv()) {
272             showContentFragment(new ErrorFragment(),
273                     R.string.user_is_not_allowed_dlg_title, R.string.user_is_not_allowed_dlg_text);
274         } else {
275             showDialogFragment(new ErrorDialogFragment(), 0, R.string.user_is_not_allowed_dlg_text);
276         }
277     }
278 
showGenericError()279     private void showGenericError() {
280         if (isTv()) {
281             showContentFragment(new ErrorFragment(),
282                     R.string.generic_error_dlg_title, R.string.generic_error_dlg_text);
283         } else {
284             showDialogFragment(new ErrorDialogFragment(), 0, R.string.generic_error_dlg_text);
285         }
286     }
287 
isTv()288     private boolean isTv() {
289         return (getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
290                 == Configuration.UI_MODE_TYPE_TELEVISION;
291     }
292 
showContentFragment(@onNull Fragment fragment, @StringRes int title, @StringRes int text)293     private void showContentFragment(@NonNull Fragment fragment, @StringRes int title,
294             @StringRes int text) {
295         Bundle args = new Bundle();
296         args.putInt(ErrorFragment.TITLE, title);
297         args.putInt(ErrorFragment.TEXT, text);
298         fragment.setArguments(args);
299 
300         getFragmentManager().beginTransaction()
301                 .replace(android.R.id.content, fragment)
302                 .commit();
303     }
304 
showDialogFragment(@onNull DialogFragment fragment, @StringRes int title, @StringRes int text)305     private void showDialogFragment(@NonNull DialogFragment fragment,
306             @StringRes int title, @StringRes int text) {
307         FragmentTransaction ft = getFragmentManager().beginTransaction();
308         Fragment prev = getFragmentManager().findFragmentByTag("dialog");
309         if (prev != null) {
310             ft.remove(prev);
311         }
312 
313         Bundle args = new Bundle();
314         if (title != 0) {
315             args.putInt(ErrorDialogFragment.TITLE, title);
316         }
317         args.putInt(ErrorDialogFragment.TEXT, text);
318 
319         fragment.setArguments(args);
320         fragment.show(ft, "dialog");
321     }
322 
323     /**
324      * Starts uninstall of app.
325      */
startUninstallProgress(boolean keepData, boolean isClonedApp)326     public void startUninstallProgress(boolean keepData, boolean isClonedApp) {
327         mIsClonedApp = isClonedApp;
328         startUninstallProgress(keepData);
329     }
330 
startUninstallProgress(boolean keepData)331     public void startUninstallProgress(boolean keepData) {
332         boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
333         CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager());
334 
335         if (isTv()) {
336             Intent newIntent = new Intent(Intent.ACTION_VIEW);
337             newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);
338             newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
339             newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
340             newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
341 
342             if (returnResult) {
343                 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
344                 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
345             }
346 
347             newIntent.setClass(this, UninstallAppProgress.class);
348             startActivity(newIntent);
349         } else if (returnResult || mDialogInfo.callback != null || getCallingActivity() != null) {
350             Intent newIntent = new Intent(this, UninstallUninstalling.class);
351 
352             newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);
353             newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
354             newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
355             newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
356             newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);
357             newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
358             if (returnResult) {
359                 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
360             }
361 
362             if (returnResult || getCallingActivity() != null) {
363                 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
364             }
365             if (mDialogInfo.deleteFlags != 0) {
366                 newIntent.putExtra(PackageInstaller.EXTRA_DELETE_FLAGS,
367                         mDialogInfo.deleteFlags);
368             }
369             startActivity(newIntent);
370         } else {
371             int uninstallId;
372             try {
373                 uninstallId = UninstallEventReceiver.getNewId(this);
374             } catch (EventResultPersister.OutOfIdsException e) {
375                 showGenericError();
376                 return;
377             }
378 
379             Intent broadcastIntent = new Intent(this, UninstallFinish.class);
380 
381             broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
382             broadcastIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
383             broadcastIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
384             broadcastIntent.putExtra(UninstallFinish.EXTRA_APP_LABEL, label);
385             broadcastIntent.putExtra(UninstallFinish.EXTRA_UNINSTALL_ID, uninstallId);
386             broadcastIntent.putExtra(UninstallFinish.EXTRA_IS_CLONE_APP, mIsClonedApp);
387 
388             PendingIntent pendingIntent =
389                     PendingIntent.getBroadcast(this, uninstallId, broadcastIntent,
390                             PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
391 
392             NotificationManager notificationManager = getSystemService(NotificationManager.class);
393             NotificationChannel uninstallingChannel = new NotificationChannel(UNINSTALLING_CHANNEL,
394                     getString(R.string.uninstalling_notification_channel),
395                     NotificationManager.IMPORTANCE_MIN);
396             notificationManager.createNotificationChannel(uninstallingChannel);
397 
398             Notification uninstallingNotification =
399                     (new Notification.Builder(this, UNINSTALLING_CHANNEL))
400                     .setSmallIcon(R.drawable.ic_remove).setProgress(0, 1, true)
401                     .setContentTitle(mIsClonedApp
402                             ? getString(R.string.uninstalling_cloned_app, label)
403                             : getString(R.string.uninstalling_app, label))
404                             .setOngoing(true)
405                     .build();
406 
407             notificationManager.notify(uninstallId, uninstallingNotification);
408 
409             try {
410                 Log.i(TAG, "Uninstalling extras=" + broadcastIntent.getExtras());
411 
412                 int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
413                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
414                 flags |= mDialogInfo.deleteFlags;
415 
416                 createContextAsUser(mDialogInfo.user, 0).getPackageManager().getPackageInstaller()
417                         .uninstall(new VersionedPackage(mDialogInfo.appInfo.packageName,
418                                 PackageManager.VERSION_CODE_HIGHEST), flags,
419                                 pendingIntent.getIntentSender());
420             } catch (Exception e) {
421                 notificationManager.cancel(uninstallId);
422 
423                 Log.e(TAG, "Cannot start uninstall", e);
424                 showGenericError();
425             }
426         }
427     }
428 
dispatchAborted()429     public void dispatchAborted() {
430         if (mDialogInfo != null && mDialogInfo.callback != null) {
431             mDialogInfo.callback.onUninstallComplete(mPackageName,
432                     PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
433         }
434     }
435 
getPackageNameForUid(int sourceUid)436     private String getPackageNameForUid(int sourceUid) {
437         String[] packagesForUid = getPackageManager().getPackagesForUid(sourceUid);
438         if (packagesForUid == null) {
439             return null;
440         }
441         return packagesForUid[0];
442     }
443 }
444