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.annotation.NonNull; 20 import android.app.Notification; 21 import android.app.NotificationChannel; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.app.admin.IDevicePolicyManager; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.IPackageManager; 30 import android.content.pm.PackageInstaller; 31 import android.content.pm.PackageManager; 32 import android.content.pm.UserInfo; 33 import android.graphics.drawable.Icon; 34 import android.os.RemoteException; 35 import android.os.ServiceManager; 36 import android.os.UserHandle; 37 import android.os.UserManager; 38 import android.provider.Settings; 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 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 */ isProfileOfOrSame(@onNull UserManager userManager, int userId, int profileId)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 */ setBigText(@onNull Notification.Builder builder, @NonNull CharSequence text)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 */ addManageUsersButton(@onNull Context context, @NonNull Notification.Builder builder)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 */ addDeviceManagerButton(@onNull Context context, @NonNull Notification.Builder builder)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