diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 83f9357d29093..5f3adda08f9e2 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5730,4 +5730,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 eacf11f474157..2ed96c606dc9d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -116,6 +116,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; @@ -274,6 +275,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; @@ -281,6 +284,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. @@ -2015,35 +2025,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) { @@ -8465,4 +8459,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); + } }