blob: 5a51ac22b88f14575036344fc55d7ac9f13fb1a1 [file] [log] [blame]
/*
* 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());
}
}