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 onReceive(Context context, Intent intent)56 public void onReceive(Context context, Intent intent) { 57 int returnCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0); 58 59 if (returnCode == PackageInstaller.STATUS_PENDING_USER_ACTION) { 60 context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT)); 61 return; 62 } 63 64 int uninstallId = intent.getIntExtra(EXTRA_UNINSTALL_ID, 0); 65 ApplicationInfo appInfo = intent.getParcelableExtra( 66 PackageUtil.INTENT_ATTR_APPLICATION_INFO); 67 String appLabel = intent.getStringExtra(EXTRA_APP_LABEL); 68 boolean allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 69 70 NotificationManager notificationManager = 71 context.getSystemService(NotificationManager.class); 72 UserManager userManager = context.getSystemService(UserManager.class); 73 74 NotificationChannel uninstallFailureChannel = new NotificationChannel( 75 UNINSTALL_FAILURE_CHANNEL, 76 context.getString(R.string.uninstall_failure_notification_channel), 77 NotificationManager.IMPORTANCE_DEFAULT); 78 notificationManager.createNotificationChannel(uninstallFailureChannel); 79 80 Notification.Builder uninstallFailedNotification = new Notification.Builder(context, 81 UNINSTALL_FAILURE_CHANNEL); 82 83 switch (returnCode) { 84 case PackageInstaller.STATUS_SUCCESS: 85 notificationManager.cancel(uninstallId); 86 87 Toast.makeText(context, context.getString(R.string.uninstall_done_app, appLabel), 88 Toast.LENGTH_LONG).show(); 89 return; 90 case PackageInstaller.STATUS_FAILURE_BLOCKED: { 91 int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0); 92 93 switch (legacyStatus) { 94 case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: { 95 IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface( 96 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); 97 // Find out if the package is an active admin for some non-current user. 98 int myUserId = UserHandle.myUserId(); 99 UserInfo otherBlockingUser = null; 100 for (UserInfo user : userManager.getUsers()) { 101 // We only catch the case when the user in question is neither the 102 // current user nor its profile. 103 if (isProfileOfOrSame(userManager, myUserId, user.id)) { 104 continue; 105 } 106 107 try { 108 if (dpm.packageHasActiveAdmins(appInfo.packageName, user.id)) { 109 otherBlockingUser = user; 110 break; 111 } 112 } catch (RemoteException e) { 113 Log.e(LOG_TAG, "Failed to talk to package manager", e); 114 } 115 } 116 if (otherBlockingUser == null) { 117 Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName 118 + " is a device admin"); 119 120 addDeviceManagerButton(context, uninstallFailedNotification); 121 setBigText(uninstallFailedNotification, context.getString( 122 R.string.uninstall_failed_device_policy_manager)); 123 } else { 124 Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName 125 + " is a device admin of user " + otherBlockingUser); 126 127 setBigText(uninstallFailedNotification, String.format(context.getString( 128 R.string.uninstall_failed_device_policy_manager_of_user), 129 otherBlockingUser.name)); 130 } 131 break; 132 } 133 case PackageManager.DELETE_FAILED_OWNER_BLOCKED: { 134 IPackageManager packageManager = IPackageManager.Stub.asInterface( 135 ServiceManager.getService("package")); 136 137 List<UserInfo> users = userManager.getUsers(); 138 int blockingUserId = UserHandle.USER_NULL; 139 for (int i = 0; i < users.size(); ++i) { 140 final UserInfo user = users.get(i); 141 try { 142 if (packageManager.getBlockUninstallForUser(appInfo.packageName, 143 user.id)) { 144 blockingUserId = user.id; 145 break; 146 } 147 } catch (RemoteException e) { 148 // Shouldn't happen. 149 Log.e(LOG_TAG, "Failed to talk to package manager", e); 150 } 151 } 152 153 int myUserId = UserHandle.myUserId(); 154 if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) { 155 addDeviceManagerButton(context, uninstallFailedNotification); 156 } else { 157 addManageUsersButton(context, uninstallFailedNotification); 158 } 159 160 if (blockingUserId == UserHandle.USER_NULL) { 161 Log.d(LOG_TAG, 162 "Uninstall failed for " + appInfo.packageName + " with code " 163 + returnCode + " no blocking user"); 164 } else if (blockingUserId == UserHandle.USER_SYSTEM) { 165 setBigText(uninstallFailedNotification, 166 context.getString(R.string.uninstall_blocked_device_owner)); 167 } else { 168 if (allUsers) { 169 setBigText(uninstallFailedNotification, 170 context.getString( 171 R.string.uninstall_all_blocked_profile_owner)); 172 } else { 173 setBigText(uninstallFailedNotification, context.getString( 174 R.string.uninstall_blocked_profile_owner)); 175 } 176 } 177 break; 178 } 179 default: 180 Log.d(LOG_TAG, "Uninstall blocked for " + appInfo.packageName 181 + " with legacy code " + legacyStatus); 182 } break; 183 } 184 default: 185 Log.d(LOG_TAG, "Uninstall failed for " + appInfo.packageName + " with code " 186 + returnCode); 187 break; 188 } 189 190 uninstallFailedNotification.setContentTitle( 191 context.getString(R.string.uninstall_failed_app, appLabel)); 192 uninstallFailedNotification.setOngoing(false); 193 uninstallFailedNotification.setSmallIcon(R.drawable.ic_error); 194 notificationManager.notify(uninstallId, uninstallFailedNotification.build()); 195 } 196 197 /** 198 * Is a profile part of a user? 199 * 200 * @param userManager The user manager 201 * @param userId The id of the user 202 * @param profileId The id of the profile 203 * 204 * @return If the profile is part of the user or the profile parent of the user 205 */ isProfileOfOrSame(@onNull UserManager userManager, int userId, int profileId)206 private boolean isProfileOfOrSame(@NonNull UserManager userManager, int userId, int profileId) { 207 if (userId == profileId) { 208 return true; 209 } 210 211 UserInfo parentUser = userManager.getProfileParent(profileId); 212 return parentUser != null && parentUser.id == userId; 213 } 214 215 /** 216 * Set big text for the notification. 217 * 218 * @param builder The builder of the notification 219 * @param text The text to set. 220 */ setBigText(@onNull Notification.Builder builder, @NonNull CharSequence text)221 private void setBigText(@NonNull Notification.Builder builder, 222 @NonNull CharSequence text) { 223 builder.setStyle(new Notification.BigTextStyle().bigText(text)); 224 } 225 226 /** 227 * Add a button to the notification that links to the user management. 228 * 229 * @param context The context the notification is created in 230 * @param builder The builder of the notification 231 */ addManageUsersButton(@onNull Context context, @NonNull Notification.Builder builder)232 private void addManageUsersButton(@NonNull Context context, 233 @NonNull Notification.Builder builder) { 234 Intent intent = new Intent(Settings.ACTION_USER_SETTINGS); 235 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); 236 237 builder.addAction((new Notification.Action.Builder( 238 Icon.createWithResource(context, R.drawable.ic_settings_multiuser), 239 context.getString(R.string.manage_users), 240 PendingIntent.getActivity(context, 0, intent, 241 PendingIntent.FLAG_UPDATE_CURRENT))).build()); 242 } 243 244 /** 245 * Add a button to the notification that links to the device policy management. 246 * 247 * @param context The context the notification is created in 248 * @param builder The builder of the notification 249 */ addDeviceManagerButton(@onNull Context context, @NonNull Notification.Builder builder)250 private void addDeviceManagerButton(@NonNull Context context, 251 @NonNull Notification.Builder builder) { 252 Intent intent = new Intent(); 253 intent.setClassName("com.android.settings", 254 "com.android.settings.Settings$DeviceAdminSettingsActivity"); 255 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); 256 257 builder.addAction((new Notification.Action.Builder( 258 Icon.createWithResource(context, R.drawable.ic_lock), 259 context.getString(R.string.manage_device_administrators), 260 PendingIntent.getActivity(context, 0, intent, 261 PendingIntent.FLAG_UPDATE_CURRENT))).build()); 262 } 263 } 264