Home | History | Annotate | Download | only in packageinstaller
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.packageinstaller;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationChannel;
     21 import android.app.NotificationManager;
     22 import android.app.PendingIntent;
     23 import android.app.admin.IDevicePolicyManager;
     24 import android.content.BroadcastReceiver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.pm.ApplicationInfo;
     28 import android.content.pm.IPackageManager;
     29 import android.content.pm.PackageInstaller;
     30 import android.content.pm.PackageManager;
     31 import android.content.pm.UserInfo;
     32 import android.graphics.drawable.Icon;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.os.UserHandle;
     36 import android.os.UserManager;
     37 import android.provider.Settings;
     38 import android.support.annotation.NonNull;
     39 import android.util.Log;
     40 import android.widget.Toast;
     41 
     42 import java.util.List;
     43 
     44 /**
     45  * Finish an uninstallation and show Toast on success or failure notification.
     46  */
     47 public class UninstallFinish extends BroadcastReceiver {
     48     private static final String LOG_TAG = UninstallFinish.class.getSimpleName();
     49 
     50     private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall failure";
     51 
     52     static final String EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID";
     53     static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
     54 
     55     @Override
     56     public void onReceive(Context context, Intent intent) {
     57         int returnCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
     58 
     59         Log.i(LOG_TAG, "Uninstall finished extras=" + intent.getExtras());
     60 
     61         if (returnCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
     62             context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));
     63             return;
     64         }
     65 
     66         int uninstallId = intent.getIntExtra(EXTRA_UNINSTALL_ID, 0);
     67         ApplicationInfo appInfo = intent.getParcelableExtra(
     68                 PackageUtil.INTENT_ATTR_APPLICATION_INFO);
     69         String appLabel = intent.getStringExtra(EXTRA_APP_LABEL);
     70         boolean allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
     71 
     72         NotificationManager notificationManager =
     73                 context.getSystemService(NotificationManager.class);
     74         UserManager userManager = context.getSystemService(UserManager.class);
     75 
     76         NotificationChannel uninstallFailureChannel = new NotificationChannel(
     77                 UNINSTALL_FAILURE_CHANNEL,
     78                 context.getString(R.string.uninstall_failure_notification_channel),
     79                 NotificationManager.IMPORTANCE_DEFAULT);
     80         notificationManager.createNotificationChannel(uninstallFailureChannel);
     81 
     82         Notification.Builder uninstallFailedNotification = new Notification.Builder(context,
     83                 UNINSTALL_FAILURE_CHANNEL);
     84 
     85         switch (returnCode) {
     86             case PackageInstaller.STATUS_SUCCESS:
     87                 notificationManager.cancel(uninstallId);
     88 
     89                 Toast.makeText(context, context.getString(R.string.uninstall_done_app, appLabel),
     90                         Toast.LENGTH_LONG).show();
     91                 return;
     92             case PackageInstaller.STATUS_FAILURE_BLOCKED: {
     93                 int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
     94 
     95                 switch (legacyStatus) {
     96                     case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
     97                         IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
     98                                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
     99                         // Find out if the package is an active admin for some non-current user.
    100                         int myUserId = UserHandle.myUserId();
    101                         UserInfo otherBlockingUser = null;
    102                         for (UserInfo user : userManager.getUsers()) {
    103                             // We only catch the case when the user in question is neither the
    104                             // current user nor its profile.
    105                             if (isProfileOfOrSame(userManager, myUserId, user.id)) {
    106                                 continue;
    107                             }
    108 
    109                             try {
    110                                 if (dpm.packageHasActiveAdmins(appInfo.packageName, user.id)) {
    111                                     otherBlockingUser = user;
    112                                     break;
    113                                 }
    114                             } catch (RemoteException e) {
    115                                 Log.e(LOG_TAG, "Failed to talk to package manager", e);
    116                             }
    117                         }
    118                         if (otherBlockingUser == null) {
    119                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
    120                                     + " is a device admin");
    121 
    122                             addDeviceManagerButton(context, uninstallFailedNotification);
    123                             setBigText(uninstallFailedNotification, context.getString(
    124                                     R.string.uninstall_failed_device_policy_manager));
    125                         } else {
    126                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
    127                                     + " is a device admin of user " + otherBlockingUser);
    128 
    129                             setBigText(uninstallFailedNotification, String.format(context.getString(
    130                                     R.string.uninstall_failed_device_policy_manager_of_user),
    131                                     otherBlockingUser.name));
    132                         }
    133                         break;
    134                     }
    135                     case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
    136                         IPackageManager packageManager = IPackageManager.Stub.asInterface(
    137                                 ServiceManager.getService("package"));
    138 
    139                         List<UserInfo> users = userManager.getUsers();
    140                         int blockingUserId = UserHandle.USER_NULL;
    141                         for (int i = 0; i < users.size(); ++i) {
    142                             final UserInfo user = users.get(i);
    143                             try {
    144                                 if (packageManager.getBlockUninstallForUser(appInfo.packageName,
    145                                         user.id)) {
    146                                     blockingUserId = user.id;
    147                                     break;
    148                                 }
    149                             } catch (RemoteException e) {
    150                                 // Shouldn't happen.
    151                                 Log.e(LOG_TAG, "Failed to talk to package manager", e);
    152                             }
    153                         }
    154 
    155                         int myUserId = UserHandle.myUserId();
    156                         if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
    157                             addDeviceManagerButton(context, uninstallFailedNotification);
    158                         } else {
    159                             addManageUsersButton(context, uninstallFailedNotification);
    160                         }
    161 
    162                         if (blockingUserId == UserHandle.USER_NULL) {
    163                             Log.d(LOG_TAG,
    164                                     "Uninstall failed for " + appInfo.packageName + " with code "
    165                                             + returnCode + " no blocking user");
    166                         } else if (blockingUserId == UserHandle.USER_SYSTEM) {
    167                             setBigText(uninstallFailedNotification,
    168                                     context.getString(R.string.uninstall_blocked_device_owner));
    169                         } else {
    170                             if (allUsers) {
    171                                 setBigText(uninstallFailedNotification,
    172                                         context.getString(
    173                                                 R.string.uninstall_all_blocked_profile_owner));
    174                             } else {
    175                                 setBigText(uninstallFailedNotification, context.getString(
    176                                         R.string.uninstall_blocked_profile_owner));
    177                             }
    178                         }
    179                         break;
    180                     }
    181                     default:
    182                         Log.d(LOG_TAG, "Uninstall blocked for " + appInfo.packageName
    183                                 + " with legacy code " + legacyStatus);
    184                 } break;
    185             }
    186             default:
    187                 Log.d(LOG_TAG, "Uninstall failed for " + appInfo.packageName + " with code "
    188                         + returnCode);
    189                 break;
    190         }
    191 
    192         uninstallFailedNotification.setContentTitle(
    193                 context.getString(R.string.uninstall_failed_app, appLabel));
    194         uninstallFailedNotification.setOngoing(false);
    195         uninstallFailedNotification.setSmallIcon(R.drawable.ic_error);
    196         notificationManager.notify(uninstallId, uninstallFailedNotification.build());
    197     }
    198 
    199     /**
    200      * Is a profile part of a user?
    201      *
    202      * @param userManager The user manager
    203      * @param userId The id of the user
    204      * @param profileId The id of the profile
    205      *
    206      * @return If the profile is part of the user or the profile parent of the user
    207      */
    208     private boolean isProfileOfOrSame(@NonNull UserManager userManager, int userId, int profileId) {
    209         if (userId == profileId) {
    210             return true;
    211         }
    212 
    213         UserInfo parentUser = userManager.getProfileParent(profileId);
    214         return parentUser != null && parentUser.id == userId;
    215     }
    216 
    217     /**
    218      * Set big text for the notification.
    219      *
    220      * @param builder The builder of the notification
    221      * @param text The text to set.
    222      */
    223     private void setBigText(@NonNull Notification.Builder builder,
    224             @NonNull CharSequence text) {
    225         builder.setStyle(new Notification.BigTextStyle().bigText(text));
    226     }
    227 
    228     /**
    229      * Add a button to the notification that links to the user management.
    230      *
    231      * @param context The context the notification is created in
    232      * @param builder The builder of the notification
    233      */
    234     private void addManageUsersButton(@NonNull Context context,
    235             @NonNull Notification.Builder builder) {
    236         Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
    237         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
    238 
    239         builder.addAction((new Notification.Action.Builder(
    240                 Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
    241                 context.getString(R.string.manage_users),
    242                 PendingIntent.getActivity(context, 0, intent,
    243                         PendingIntent.FLAG_UPDATE_CURRENT))).build());
    244     }
    245 
    246     /**
    247      * Add a button to the notification that links to the device policy management.
    248      *
    249      * @param context The context the notification is created in
    250      * @param builder The builder of the notification
    251      */
    252     private void addDeviceManagerButton(@NonNull Context context,
    253             @NonNull Notification.Builder builder) {
    254         Intent intent = new Intent();
    255         intent.setClassName("com.android.settings",
    256                 "com.android.settings.Settings$DeviceAdminSettingsActivity");
    257         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
    258 
    259         builder.addAction((new Notification.Action.Builder(
    260                 Icon.createWithResource(context, R.drawable.ic_lock),
    261                 context.getString(R.string.manage_device_administrators),
    262                 PendingIntent.getActivity(context, 0, intent,
    263                         PendingIntent.FLAG_UPDATE_CURRENT))).build());
    264     }
    265 }
    266