Add test method to remove admins.

Add test method to remove admins that declare
FLAG_TEST_APP without informing them.
The method will also remove the device and profile
owner status of the admin.

Bug: 28027468
Change-Id: Idb4d3299a9c6595c94bfb424546cd8a384131835
diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
index b83484d..31c7421 100644
--- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java
+++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
@@ -44,6 +44,7 @@
     private static final String COMMAND_SET_ACTIVE_ADMIN = "set-active-admin";
     private static final String COMMAND_SET_DEVICE_OWNER = "set-device-owner";
     private static final String COMMAND_SET_PROFILE_OWNER = "set-profile-owner";
+    private static final String COMMAND_REMOVE_ACTIVE_ADMIN = "remove-active-admin";
 
     private IDevicePolicyManager mDevicePolicyManager;
     private int mUserId = UserHandle.USER_SYSTEM;
@@ -60,6 +61,8 @@
                 "[ --name <NAME> ] <COMPONENT>\n" +
                 "usage: dpm set-profile-owner [ --user <USER_ID> | current ] [ --name <NAME> ] " +
                 "<COMPONENT>\n" +
+                "usage: dpm remove-active-admin [ --user <USER_ID> | current ] [ --name <NAME> ] " +
+                "<COMPONENT>\n" +
                 "\n" +
                 "dpm set-active-admin: Sets the given component as active admin" +
                 " for an existing user.\n" +
@@ -68,7 +71,11 @@
                 " package as device owner.\n" +
                 "\n" +
                 "dpm set-profile-owner: Sets the given component as active admin and profile" +
-                " owner for an existing user.\n");
+                " owner for an existing user.\n" +
+                "\n" +
+                "dpm remove-active-admin: Disables an active admin, the admin must have declared" +
+                " android:testOnly in the application in its manifest. This will also remove" +
+                " device and profile owners\n");
     }
 
     @Override
@@ -91,6 +98,9 @@
             case COMMAND_SET_PROFILE_OWNER:
                 runSetProfileOwner();
                 break;
+            case COMMAND_REMOVE_ACTIVE_ADMIN:
+                runRemoveActiveAdmin();
+                break;
             default:
                 throw new IllegalArgumentException ("unknown command '" + command + "'");
         }
@@ -152,6 +162,12 @@
         System.out.println("Active admin set to component " + mComponent.toShortString());
     }
 
+    private void runRemoveActiveAdmin() throws RemoteException {
+        parseArgs(/*canHaveName=*/ false);
+        mDevicePolicyManager.forceRemoveActiveAdmin(mComponent, mUserId);
+        System.out.println("Success: Admin removed " + mComponent);
+    }
+
     private void runSetProfileOwner() throws RemoteException {
         parseArgs(/*canHaveName=*/ true);
         mDevicePolicyManager.setActiveAdmin(mComponent, true /*refreshing*/, mUserId);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7a18df6..0ca2e14 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6391,6 +6391,24 @@
         }
     }
 
+    /**
+     * @hide
+     * Remove a test admin synchronously without sending it a broadcast about being removed.
+     * If the admin is a profile owner or device owner it will still be removed.
+     *
+     * @param userHandle user id to remove the admin for.
+     * @param admin The administration compononent to remove.
+     * @throws SecurityException if the caller is not shell / root or the admin package
+     *         isn't a test application see {@link ApplicationInfo#FLAG_TEST_APP}.
+     */
+    public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) {
+        try {
+            mService.forceRemoveActiveAdmin(adminReceiver, userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     private void throwIfParentInstance(String functionName) {
         if (mParentInstance) {
             throw new SecurityException(functionName + " cannot be called on the parent instance");
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index cba64c2..989e613 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -111,6 +111,7 @@
     boolean packageHasActiveAdmins(String packageName, int userHandle);
     void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result, int userHandle);
     void removeActiveAdmin(in ComponentName policyReceiver, int userHandle);
+    void forceRemoveActiveAdmin(in ComponentName policyReceiver, int userHandle);
     boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
 
     void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d3d05f3..45a7311 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2909,6 +2909,54 @@
         }
     }
 
+    public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(adminReceiver, "ComponentName is null");
+        enforceShell("forceRemoveActiveAdmin");
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            final ApplicationInfo ai;
+            try {
+                ai = mIPackageManager.getApplicationInfo(adminReceiver.getPackageName(),
+                        0, userHandle);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            if (ai == null) {
+                throw new IllegalStateException("Couldn't find package to remove admin "
+                        + adminReceiver.getPackageName() + " " + userHandle);
+            }
+            if ((ai.flags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
+                throw new SecurityException("Attempt to remove non-test admin " + adminReceiver
+                        + adminReceiver + " " + userHandle);
+            }
+            // If admin is a device or profile owner tidy that up first.
+            synchronized (this)  {
+                if (isDeviceOwner(adminReceiver, userHandle)) {
+                    clearDeviceOwnerLocked(getDeviceOwnerAdminLocked(), userHandle);
+                }
+                if (isProfileOwner(adminReceiver, userHandle)) {
+                    final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver,
+                            userHandle, /* parent */ false);
+                    clearProfileOwnerLocked(admin, userHandle);
+                }
+            }
+            // Remove the admin skipping sending the broadcast.
+            removeAdminArtifacts(adminReceiver, userHandle);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
+    private void enforceShell(String method) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
+            throw new SecurityException("Non-shell user attempted to call " + method);
+        }
+    }
+
     @Override
     public void removeActiveAdmin(ComponentName adminReceiver, int userHandle) {
         if (!mHasFeature) {
@@ -5732,32 +5780,37 @@
             enforceUserUnlocked(deviceOwnerUserId);
 
             final ActiveAdmin admin = getDeviceOwnerAdminLocked();
-            if (admin != null) {
-                admin.disableCamera = false;
-                admin.userRestrictions = null;
-                admin.forceEphemeralUsers = false;
-                mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
-            }
-            clearUserPoliciesLocked(deviceOwnerUserId);
-
-            mOwners.clearDeviceOwner();
-            mOwners.writeDeviceOwner();
-            updateDeviceOwnerLocked();
-            disableSecurityLoggingIfNotCompliant();
-            // Reactivate backup service.
             long ident = mInjector.binderClearCallingIdentity();
             try {
-                mInjector.getIBackupManager().setBackupServiceActive(UserHandle.USER_SYSTEM, true);
-
+                clearDeviceOwnerLocked(admin, deviceOwnerUserId);
                 removeActiveAdminLocked(deviceOwnerComponent, deviceOwnerUserId);
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Failed reactivating backup service.", e);
             } finally {
                 mInjector.binderRestoreCallingIdentity(ident);
             }
         }
     }
 
+    private void clearDeviceOwnerLocked(ActiveAdmin admin, int userId) {
+        if (admin != null) {
+            admin.disableCamera = false;
+            admin.userRestrictions = null;
+            admin.forceEphemeralUsers = false;
+            mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
+        }
+        clearUserPoliciesLocked(userId);
+
+        mOwners.clearDeviceOwner();
+        mOwners.writeDeviceOwner();
+        updateDeviceOwnerLocked();
+        disableSecurityLoggingIfNotCompliant();
+        try {
+            // Reactivate backup service.
+            mInjector.getIBackupManager().setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed reactivating backup service.", e);
+        }
+    }
+
     @Override
     public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
         if (!mHasFeature) {
@@ -5794,14 +5847,9 @@
         final ActiveAdmin admin =
                 getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         synchronized (this) {
-            admin.disableCamera = false;
-            admin.userRestrictions = null;
-            clearUserPoliciesLocked(userId);
-            mOwners.removeProfileOwner(userId);
-            mOwners.writeProfileOwner(userId);
-
             final long ident = mInjector.binderClearCallingIdentity();
             try {
+                clearProfileOwnerLocked(admin, userId);
                 removeActiveAdminLocked(who, userId);
             } finally {
                 mInjector.binderRestoreCallingIdentity(ident);
@@ -5809,6 +5857,16 @@
         }
     }
 
+    public void clearProfileOwnerLocked(ActiveAdmin admin, int userId) {
+        if (admin != null) {
+            admin.disableCamera = false;
+            admin.userRestrictions = null;
+        }
+        clearUserPoliciesLocked(userId);
+        mOwners.removeProfileOwner(userId);
+        mOwners.writeProfileOwner(userId);
+    }
+
     @Override
     public void setDeviceOwnerLockScreenInfo(ComponentName who, CharSequence info) {
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5842,15 +5900,13 @@
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         saveSettingsLocked(userId);
 
-        final long ident = mInjector.binderClearCallingIdentity();
         try {
             mIPackageManager.updatePermissionFlagsForAllApps(
                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                     0  /* flagValues */, userId);
             pushUserRestrictions(userId);
         } catch (RemoteException re) {
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
+            // Shouldn't happen.
         }
     }