| /* |
| * 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.annotation.NonNull; |
| 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.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<UserInfo> 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()); |
| } |
| } |