From a2ae2238e506d5b092015440792d5a885b7a7b1d Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Thu, 28 Jan 2016 18:05:14 -0800 Subject: [PATCH] Added an api to uninstall a packge with active DAs The api deactivates all the active admins in the package, then force stops the package and starts the uninstall intent for the package. This is intended to provide an easy way for a user to delete a misbehaving Device Admin Bug: b/22359208 cherrypick of Ic7ddd89ef6db53e7e76f805808d9e806100374db Change-Id: I0d677839120c46f22231a7d6f9cf6630cb020227 --- .../app/admin/DevicePolicyManager.java | 28 +++ .../app/admin/IDevicePolicyManager.aidl | 3 + .../DevicePolicyManagerService.java | 159 +++++++++++++++--- 3 files changed, 171 insertions(+), 19 deletions(-) diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 02eb115175fd1..f06979a2c42ca 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5722,4 +5722,32 @@ public class DevicePolicyManager { return false; } } + + /** + * @hide + * Returns whether the uninstall for {@code packageName} for the current user is in queue + * to be started + * @param packageName the package to check for + * @return whether the uninstall intent for {@code packageName} is pending + */ + public boolean isUninstallInQueue(String packageName) { + try { + return mService.isUninstallInQueue(packageName); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + return false; + } + } + + /** + * @hide + * @param packageName the package containing active DAs to be uninstalled + */ + public void uninstallPackageWithActiveAdmins(String packageName) { + try { + mService.uninstallPackageWithActiveAdmins(packageName); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index c6a53443b51cb..2758f41091827 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -293,4 +293,7 @@ interface IDevicePolicyManager { boolean getDeviceLoggingEnabled(in ComponentName admin); ParceledListSlice retrieveDeviceLogs(in ComponentName admin); ParceledListSlice retrievePreviousDeviceLogs(in ComponentName admin); + + boolean isUninstallInQueue(String packageName); + void uninstallPackageWithActiveAdmins(String packageName); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3fb5a0d39b84c..e48a261717b70 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -115,6 +115,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -273,6 +274,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final int PROFILE_KEYGUARD_FEATURES = PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER | PROFILE_KEYGUARD_FEATURES_AFFECT_PROFILE; + private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000; + final Context mContext; final Injector mInjector; final IPackageManager mIPackageManager; @@ -280,6 +283,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final UserManagerInternal mUserManagerInternal; private final LockPatternUtils mLockPatternUtils; + /** + * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p + * is requested for user u. + */ + private final Set> mPackagesToRemove = + new ArraySet>(); + final LocalService mLocalService; // Stores and loads state on device and profile owners. @@ -2014,35 +2024,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); if (admin != null) { getUserData(userHandle).mRemovingAdmins.add(adminReceiver); - sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - synchronized (DevicePolicyManagerService.this) { - int userHandle = admin.getUserHandle().getIdentifier(); - DevicePolicyData policy = getUserData(userHandle); - boolean doProxyCleanup = admin.info.usesPolicy( - DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); - policy.mAdminList.remove(admin); - policy.mAdminMap.remove(adminReceiver); - validatePasswordOwnerLocked(policy); - if (doProxyCleanup) { - resetGlobalProxyLocked(getUserData(userHandle)); - } - saveSettingsLocked(userHandle); - updateMaximumTimeToLockLocked(userHandle); - policy.mRemovingAdmins.remove(adminReceiver); - } - // The removed admin might have disabled camera, so update user - // restrictions. - pushUserRestrictions(userHandle); + removeAdminArtifacts(adminReceiver, userHandle); + removePackageIfRequired(adminReceiver.getPackageName(), userHandle); } }); } } + public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle, boolean throwForMissiongPermission) { if (!mHasFeature) { @@ -8446,4 +8440,131 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { List logs = mSecurityLogMonitor.retrieveLogs(); return logs != null ? new ParceledListSlice(logs) : null; } + + private void enforceCanManageDeviceAdmin() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS, + null); + } + + @Override + public boolean isUninstallInQueue(final String packageName) { + enforceCanManageDeviceAdmin(); + final int userId = mInjector.userHandleGetCallingUserId(); + Pair packageUserPair = new Pair<>(packageName, userId); + synchronized (this) { + return mPackagesToRemove.contains(packageUserPair); + } + } + + @Override + public void uninstallPackageWithActiveAdmins(final String packageName) { + enforceCanManageDeviceAdmin(); + Preconditions.checkArgument(!TextUtils.isEmpty(packageName)); + + final int userId = mInjector.userHandleGetCallingUserId(); + + final ComponentName profileOwner = getProfileOwner(userId); + if (profileOwner != null && packageName.equals(profileOwner.getPackageName())) { + throw new IllegalArgumentException("Cannot uninstall a package with a profile owner"); + } + + final ComponentName deviceOwner = getDeviceOwnerComponent(/* callingUserOnly= */ false); + if (getDeviceOwnerUserId() == userId && deviceOwner != null + && packageName.equals(deviceOwner.getPackageName())) { + throw new IllegalArgumentException("Cannot uninstall a package with a device owner"); + } + + final Pair packageUserPair = new Pair<>(packageName, userId); + synchronized (this) { + mPackagesToRemove.add(packageUserPair); + } + + final List activeAdminsList = getActiveAdmins(userId); + if (activeAdminsList == null || activeAdminsList.size() == 0) { + startUninstallIntent(packageName, userId); + return; + } + + for (ComponentName activeAdmin : activeAdminsList) { + if (packageName.equals(activeAdmin.getPackageName())) { + removeActiveAdmin(activeAdmin, userId); + } + } + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + for (ComponentName activeAdmin : activeAdminsList) { + removeAdminArtifacts(activeAdmin, userId); + } + startUninstallIntent(packageName, userId); + } + }, DEVICE_ADMIN_DEACTIVATE_TIMEOUT); // Start uninstall after timeout anyway. + } + + private void removePackageIfRequired(final String packageName, final int userId) { + if (!packageHasActiveAdmins(packageName, userId)) { + // Will not do anything if uninstall was not requested or was already started. + startUninstallIntent(packageName, userId); + } + } + + private void startUninstallIntent(final String packageName, final int userId) { + final Pair packageUserPair = new Pair<>(packageName, userId); + synchronized (this) { + if (!mPackagesToRemove.contains(packageUserPair)) { + // Do nothing if uninstall was not requested or was already started. + return; + } + mPackagesToRemove.remove(packageUserPair); + } + try { + if (mInjector.getIPackageManager().getPackageInfo(packageName, 0, userId) == null) { + // Package does not exist. Nothing to do. + return; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Failure talking to PackageManager while getting package info"); + } + + try { // force stop the package before uninstalling + mInjector.getIActivityManager().forceStopPackage(packageName, userId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Failure talking to ActivityManager while force stopping package"); + } + final Uri packageURI = Uri.parse("package:" + packageName); + final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); + uninstallIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivityAsUser(uninstallIntent, UserHandle.of(userId)); + } + + /** + * Removes the admin from the policy. Ideally called after the admin's + * {@link DeviceAdminReceiver#onDisabled(Context, Intent)} has been successfully completed. + * + * @param adminReceiver The admin to remove + * @param userHandle The user for which this admin has to be removed. + */ + private void removeAdminArtifacts(final ComponentName adminReceiver, final int userHandle) { + synchronized (this) { + final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); + if (admin == null) { + return; + } + final DevicePolicyData policy = getUserData(userHandle); + final boolean doProxyCleanup = admin.info.usesPolicy( + DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); + policy.mAdminList.remove(admin); + policy.mAdminMap.remove(adminReceiver); + validatePasswordOwnerLocked(policy); + if (doProxyCleanup) { + resetGlobalProxyLocked(policy); + } + saveSettingsLocked(userHandle); + updateMaximumTimeToLockLocked(userHandle); + policy.mRemovingAdmins.remove(adminReceiver); + } + // The removed admin might have disabled camera, so update user + // restrictions. + pushUserRestrictions(userHandle); + } }