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.DevicePolicyManager; 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.PackageInstaller; 29 import android.content.pm.PackageManager; 30 import android.graphics.drawable.Icon; 31 import android.os.Process; 32 import android.os.UserHandle; 33 import android.os.UserManager; 34 import android.provider.Settings; 35 import android.util.Log; 36 import android.widget.Toast; 37 38 import androidx.annotation.NonNull; 39 40 import java.util.List; 41 42 /** 43 * Finish an uninstallation and show Toast on success or failure notification. 44 */ 45 public class UninstallFinish extends BroadcastReceiver { 46 private static final String LOG_TAG = UninstallFinish.class.getSimpleName(); 47 48 private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall failure"; 49 50 static final String EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID"; 51 static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"; 52 static final String EXTRA_IS_CLONE_APP = "com.android.packageinstaller.extra.IS_CLONE_APP"; 53 54 @Override onReceive(Context context, Intent intent)55 public void onReceive(Context context, Intent intent) { 56 int returnCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0); 57 58 Log.i(LOG_TAG, "Uninstall finished extras=" + intent.getExtras()); 59 60 if (returnCode == PackageInstaller.STATUS_PENDING_USER_ACTION) { 61 context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT)); 62 return; 63 } 64 65 int uninstallId = intent.getIntExtra(EXTRA_UNINSTALL_ID, 0); 66 ApplicationInfo appInfo = intent.getParcelableExtra( 67 PackageUtil.INTENT_ATTR_APPLICATION_INFO); 68 String appLabel = intent.getStringExtra(EXTRA_APP_LABEL); 69 boolean allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 70 71 NotificationManager notificationManager = 72 context.getSystemService(NotificationManager.class); 73 UserManager userManager = context.getSystemService(UserManager.class); 74 75 NotificationChannel uninstallFailureChannel = new NotificationChannel( 76 UNINSTALL_FAILURE_CHANNEL, 77 context.getString(R.string.uninstall_failure_notification_channel), 78 NotificationManager.IMPORTANCE_DEFAULT); 79 notificationManager.createNotificationChannel(uninstallFailureChannel); 80 81 Notification.Builder uninstallFailedNotification = new Notification.Builder(context, 82 UNINSTALL_FAILURE_CHANNEL); 83 84 switch (returnCode) { 85 case PackageInstaller.STATUS_SUCCESS: 86 notificationManager.cancel(uninstallId); 87 88 boolean isCloneApp = intent.getBooleanExtra(EXTRA_IS_CLONE_APP, false); 89 Toast.makeText(context, isCloneApp 90 ? context.getString(R.string.uninstall_done_clone_app, appLabel) 91 : context.getString(R.string.uninstall_done_app, appLabel), 92 Toast.LENGTH_LONG).show(); 93 return; 94 case PackageInstaller.STATUS_FAILURE_BLOCKED: { 95 int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0); 96 97 switch (legacyStatus) { 98 case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: { 99 // Find out if the package is an active admin for some non-current user. 100 UserHandle myUserHandle = Process.myUserHandle(); 101 UserHandle otherBlockingUserHandle = null; 102 for (UserHandle otherUserHandle : userManager.getUserHandles(true)) { 103 // We only catch the case when the user in question is neither the 104 // current user nor its profile. 105 if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) { 106 continue; 107 } 108 DevicePolicyManager dpm = 109 context.createContextAsUser(otherUserHandle, 0) 110 .getSystemService(DevicePolicyManager.class); 111 if (dpm.packageHasActiveAdmins(appInfo.packageName)) { 112 otherBlockingUserHandle = otherUserHandle; 113 break; 114 } 115 } 116 if (otherBlockingUserHandle == 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 " + otherBlockingUserHandle); 126 127 String userName = 128 context.createContextAsUser(otherBlockingUserHandle, 0) 129 .getSystemService(UserManager.class).getUserName(); 130 setBigText(uninstallFailedNotification, String.format(context.getString( 131 R.string.uninstall_failed_device_policy_manager_of_user), 132 userName)); 133 } 134 break; 135 } 136 case PackageManager.DELETE_FAILED_OWNER_BLOCKED: { 137 PackageManager packageManager = context.getPackageManager(); 138 List<UserHandle> userHandles = userManager.getUserHandles(true); 139 UserHandle otherBlockingUserHandle = null; 140 for (int i = 0; i < userHandles.size(); ++i) { 141 final UserHandle handle = userHandles.get(i); 142 if (packageManager.canUserUninstall(appInfo.packageName, handle)) { 143 otherBlockingUserHandle = handle; 144 break; 145 } 146 } 147 148 UserHandle myUserHandle = Process.myUserHandle(); 149 if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) { 150 addDeviceManagerButton(context, uninstallFailedNotification); 151 } else { 152 addManageUsersButton(context, uninstallFailedNotification); 153 } 154 155 if (otherBlockingUserHandle == null) { 156 Log.d(LOG_TAG, 157 "Uninstall failed for " + appInfo.packageName + " with code " 158 + returnCode + " no blocking user"); 159 } else if (otherBlockingUserHandle == UserHandle.SYSTEM) { 160 setBigText(uninstallFailedNotification, 161 context.getString(R.string.uninstall_blocked_device_owner)); 162 } else { 163 if (allUsers) { 164 setBigText(uninstallFailedNotification, 165 context.getString( 166 R.string.uninstall_all_blocked_profile_owner)); 167 } else { 168 setBigText(uninstallFailedNotification, context.getString( 169 R.string.uninstall_blocked_profile_owner)); 170 } 171 } 172 break; 173 } 174 default: 175 Log.d(LOG_TAG, "Uninstall blocked for " + appInfo.packageName 176 + " with legacy code " + legacyStatus); 177 } break; 178 } 179 default: 180 Log.d(LOG_TAG, "Uninstall failed for " + appInfo.packageName + " with code " 181 + returnCode); 182 break; 183 } 184 185 uninstallFailedNotification.setContentTitle( 186 context.getString(R.string.uninstall_failed_app, appLabel)); 187 uninstallFailedNotification.setOngoing(false); 188 uninstallFailedNotification.setSmallIcon(R.drawable.ic_error); 189 notificationManager.notify(uninstallId, uninstallFailedNotification.build()); 190 } 191 192 /** 193 * Is a profile part of a user? 194 * 195 * @param userManager The user manager 196 * @param userHandle The handle of the user 197 * @param profileHandle The handle of the profile 198 * 199 * @return If the profile is part of the user or the profile parent of the user 200 */ isProfileOfOrSame(UserManager userManager, UserHandle userHandle, UserHandle profileHandle)201 private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle, 202 UserHandle profileHandle) { 203 if (userHandle.equals(profileHandle)) { 204 return true; 205 } 206 return userManager.getProfileParent(profileHandle) != null 207 && userManager.getProfileParent(profileHandle).equals(userHandle); 208 } 209 210 /** 211 * Set big text for the notification. 212 * 213 * @param builder The builder of the notification 214 * @param text The text to set. 215 */ setBigText(@onNull Notification.Builder builder, @NonNull CharSequence text)216 private void setBigText(@NonNull Notification.Builder builder, 217 @NonNull CharSequence text) { 218 builder.setStyle(new Notification.BigTextStyle().bigText(text)); 219 } 220 221 /** 222 * Add a button to the notification that links to the user management. 223 * 224 * @param context The context the notification is created in 225 * @param builder The builder of the notification 226 */ addManageUsersButton(@onNull Context context, @NonNull Notification.Builder builder)227 private void addManageUsersButton(@NonNull Context context, 228 @NonNull Notification.Builder builder) { 229 Intent intent = new Intent(Settings.ACTION_USER_SETTINGS); 230 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); 231 232 builder.addAction((new Notification.Action.Builder( 233 Icon.createWithResource(context, R.drawable.ic_settings_multiuser), 234 context.getString(R.string.manage_users), 235 PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT 236 | PendingIntent.FLAG_IMMUTABLE))).build()); 237 } 238 239 /** 240 * Add a button to the notification that links to the device policy management. 241 * 242 * @param context The context the notification is created in 243 * @param builder The builder of the notification 244 */ addDeviceManagerButton(@onNull Context context, @NonNull Notification.Builder builder)245 private void addDeviceManagerButton(@NonNull Context context, 246 @NonNull Notification.Builder builder) { 247 Intent intent = new Intent(); 248 intent.setClassName("com.android.settings", 249 "com.android.settings.Settings$DeviceAdminSettingsActivity"); 250 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); 251 252 builder.addAction((new Notification.Action.Builder( 253 Icon.createWithResource(context, R.drawable.ic_lock), 254 context.getString(R.string.manage_device_administrators), 255 PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT 256 | PendingIntent.FLAG_IMMUTABLE))).build()); 257 } 258 } 259