/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.packageinstaller; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.admin.IDevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.graphics.drawable.Icon; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.support.annotation.NonNull; import android.util.Log; import android.widget.Toast; import java.util.List; /** * Finish an uninstallation and show Toast on success or failure notification. */ public class UninstallFinish extends BroadcastReceiver { private static final String LOG_TAG = UninstallFinish.class.getSimpleName(); private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall failure"; static final String EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID"; static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"; @Override public void onReceive(Context context, Intent intent) { int returnCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0); Log.i(LOG_TAG, "Uninstall finished extras=" + intent.getExtras()); if (returnCode == PackageInstaller.STATUS_PENDING_USER_ACTION) { context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT)); return; } int uninstallId = intent.getIntExtra(EXTRA_UNINSTALL_ID, 0); ApplicationInfo appInfo = intent.getParcelableExtra( PackageUtil.INTENT_ATTR_APPLICATION_INFO); String appLabel = intent.getStringExtra(EXTRA_APP_LABEL); boolean allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); NotificationManager notificationManager = context.getSystemService(NotificationManager.class); UserManager userManager = context.getSystemService(UserManager.class); NotificationChannel uninstallFailureChannel = new NotificationChannel( UNINSTALL_FAILURE_CHANNEL, context.getString(R.string.uninstall_failure_notification_channel), NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(uninstallFailureChannel); Notification.Builder uninstallFailedNotification = new Notification.Builder(context, UNINSTALL_FAILURE_CHANNEL); switch (returnCode) { case PackageInstaller.STATUS_SUCCESS: notificationManager.cancel(uninstallId); Toast.makeText(context, context.getString(R.string.uninstall_done_app, appLabel), Toast.LENGTH_LONG).show(); return; case PackageInstaller.STATUS_FAILURE_BLOCKED: { int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0); switch (legacyStatus) { case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: { IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface( ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); // Find out if the package is an active admin for some non-current user. int myUserId = UserHandle.myUserId(); UserInfo otherBlockingUser = null; for (UserInfo user : userManager.getUsers()) { // We only catch the case when the user in question is neither the // current user nor its profile. if (isProfileOfOrSame(userManager, myUserId, user.id)) { continue; } try { if (dpm.packageHasActiveAdmins(appInfo.packageName, user.id)) { otherBlockingUser = user; break; } } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to talk to package manager", e); } } if (otherBlockingUser == null) { Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName + " is a device admin"); addDeviceManagerButton(context, uninstallFailedNotification); setBigText(uninstallFailedNotification, context.getString( R.string.uninstall_failed_device_policy_manager)); } else { Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName + " is a device admin of user " + otherBlockingUser); setBigText(uninstallFailedNotification, String.format(context.getString( R.string.uninstall_failed_device_policy_manager_of_user), otherBlockingUser.name)); } break; } case PackageManager.DELETE_FAILED_OWNER_BLOCKED: { IPackageManager packageManager = IPackageManager.Stub.asInterface( ServiceManager.getService("package")); List users = userManager.getUsers(); int blockingUserId = UserHandle.USER_NULL; for (int i = 0; i < users.size(); ++i) { final UserInfo user = users.get(i); try { if (packageManager.getBlockUninstallForUser(appInfo.packageName, user.id)) { blockingUserId = user.id; break; } } catch (RemoteException e) { // Shouldn't happen. Log.e(LOG_TAG, "Failed to talk to package manager", e); } } int myUserId = UserHandle.myUserId(); if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) { addDeviceManagerButton(context, uninstallFailedNotification); } else { addManageUsersButton(context, uninstallFailedNotification); } if (blockingUserId == UserHandle.USER_NULL) { Log.d(LOG_TAG, "Uninstall failed for " + appInfo.packageName + " with code " + returnCode + " no blocking user"); } else if (blockingUserId == UserHandle.USER_SYSTEM) { setBigText(uninstallFailedNotification, context.getString(R.string.uninstall_blocked_device_owner)); } else { if (allUsers) { setBigText(uninstallFailedNotification, context.getString( R.string.uninstall_all_blocked_profile_owner)); } else { setBigText(uninstallFailedNotification, context.getString( R.string.uninstall_blocked_profile_owner)); } } break; } default: Log.d(LOG_TAG, "Uninstall blocked for " + appInfo.packageName + " with legacy code " + legacyStatus); } break; } default: Log.d(LOG_TAG, "Uninstall failed for " + appInfo.packageName + " with code " + returnCode); break; } uninstallFailedNotification.setContentTitle( context.getString(R.string.uninstall_failed_app, appLabel)); uninstallFailedNotification.setOngoing(false); uninstallFailedNotification.setSmallIcon(R.drawable.ic_error); notificationManager.notify(uninstallId, uninstallFailedNotification.build()); } /** * Is a profile part of a user? * * @param userManager The user manager * @param userId The id of the user * @param profileId The id of the profile * * @return If the profile is part of the user or the profile parent of the user */ private boolean isProfileOfOrSame(@NonNull UserManager userManager, int userId, int profileId) { if (userId == profileId) { return true; } UserInfo parentUser = userManager.getProfileParent(profileId); return parentUser != null && parentUser.id == userId; } /** * Set big text for the notification. * * @param builder The builder of the notification * @param text The text to set. */ private void setBigText(@NonNull Notification.Builder builder, @NonNull CharSequence text) { builder.setStyle(new Notification.BigTextStyle().bigText(text)); } /** * Add a button to the notification that links to the user management. * * @param context The context the notification is created in * @param builder The builder of the notification */ private void addManageUsersButton(@NonNull Context context, @NonNull Notification.Builder builder) { Intent intent = new Intent(Settings.ACTION_USER_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); builder.addAction((new Notification.Action.Builder( Icon.createWithResource(context, R.drawable.ic_settings_multiuser), context.getString(R.string.manage_users), PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))).build()); } /** * Add a button to the notification that links to the device policy management. * * @param context The context the notification is created in * @param builder The builder of the notification */ private void addDeviceManagerButton(@NonNull Context context, @NonNull Notification.Builder builder) { Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.Settings$DeviceAdminSettingsActivity"); intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); builder.addAction((new Notification.Action.Builder( Icon.createWithResource(context, R.drawable.ic_lock), context.getString(R.string.manage_device_administrators), PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))).build()); } }