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