Wipe only managed profile when max number of incorrect passwords exceeded

When the maximum number of retries that has been exceeded is not for the primary profile of the user, wipe only the profile that set that policy (e.g. the managed profile) rather than the entire user. At the moment the whole device is wiped if the max number of incorrect passwords for a managed profile is reached, as the password is shared with the USER_OWNER.

Bug: 14453697
Change-Id: I5746de104133c0ea0a51d75b9c92e1516d365d8c
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index 7727e4a..724b560 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -57,7 +57,7 @@
     }
 
     public KeyguardSecurityContainer(Context context) {
-        this(null, null, 0);
+        this(context, null, 0);
     }
 
     public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
@@ -240,10 +240,13 @@
 
         boolean showTimeout = false;
         if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
-            // If we reach this code, it means the user has installed a DevicePolicyManager
-            // that requests device wipe after N attempts.  Once we get below the grace
-            // period, we'll post this dialog every time as a clear warning until the
-            // bombshell hits and the device is wiped.
+            // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
+            // N attempts. Once we get below the grace period, we post this dialog every time as a
+            // clear warning until the deletion fires.
+            //
+            // TODO: Show a different dialog depending on whether the device will be completely
+            // wiped (i.e. policy is set for the primary profile of the USER_OWNER) or a single
+            // secondary user or managed profile will be removed.
             if (remainingBeforeWipe > 0) {
                 showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe);
             } else {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c6730bf..7f2018e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2229,30 +2229,39 @@
         }
         enforceCrossUserPermission(userHandle);
         synchronized (this) {
-            int count = 0;
+            ActiveAdmin admin = (who != null) ? getActiveAdminUncheckedLocked(who, userHandle)
+                    : getAdminWithMinimumFailedPasswordsForWipeLocked(userHandle);
+            return admin != null ? admin.maximumFailedPasswordsForWipe : 0;
+        }
+    }
 
-            if (who != null) {
-                ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
-                return admin != null ? admin.maximumFailedPasswordsForWipe : count;
-            }
+    /**
+     * Returns the admin with the strictest policy on maximum failed passwords for this user and all
+     * profiles that are visible from this user. If the policy for the primary and any other profile
+     * are equal, it returns the admin for the primary profile.
+     * Returns {@code null} if none of them have that policy set.
+     */
+    private ActiveAdmin getAdminWithMinimumFailedPasswordsForWipeLocked(int userHandle) {
+        int count = 0;
+        ActiveAdmin strictestAdmin = null;
+        for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
+            DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier());
+            for (ActiveAdmin admin : policy.mAdminList) {
+                if (admin.maximumFailedPasswordsForWipe ==
+                        ActiveAdmin.DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
+                    continue;  // No max number of failed passwords policy set for this profile.
+                }
 
-            // Return strictest policy for this user and profiles that are visible from this user.
-            List<UserInfo> profiles = mUserManager.getProfiles(userHandle);
-            for (UserInfo userInfo : profiles) {
-                DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier());
-                final int N = policy.mAdminList.size();
-                for (int i=0; i<N; i++) {
-                    ActiveAdmin admin = policy.mAdminList.get(i);
-                    if (count == 0) {
-                        count = admin.maximumFailedPasswordsForWipe;
-                    } else if (admin.maximumFailedPasswordsForWipe != 0
-                            && count > admin.maximumFailedPasswordsForWipe) {
-                        count = admin.maximumFailedPasswordsForWipe;
-                    }
+                // We always favor the primary profile if several profiles have the same value set.
+                if (count == 0 ||
+                        count > admin.maximumFailedPasswordsForWipe ||
+                        (userInfo.isPrimary() && count >= admin.maximumFailedPasswordsForWipe)) {
+                    count = admin.maximumFailedPasswordsForWipe;
+                    strictestAdmin = admin;
                 }
             }
-            return count;
         }
+        return strictestAdmin;
     }
 
     public boolean resetPassword(String password, int flags, int userHandle) {
@@ -2627,7 +2636,9 @@
                 public void run() {
                     try {
                         ActivityManagerNative.getDefault().switchUser(UserHandle.USER_OWNER);
-                        mUserManager.removeUser(userHandle);
+                        if (!mUserManager.removeUser(userHandle)) {
+                            Slog.w(LOG_TAG, "Couldn't remove user " + userHandle);
+                        }
                     } catch (RemoteException re) {
                         // Shouldn't happen
                     }
@@ -2751,9 +2762,14 @@
                 policy.mFailedPasswordAttempts++;
                 saveSettingsLocked(userHandle);
                 if (mHasFeature) {
-                    int max = getMaximumFailedPasswordsForWipe(null, userHandle);
+                    ActiveAdmin strictestAdmin =
+                            getAdminWithMinimumFailedPasswordsForWipeLocked(userHandle);
+                    int max = strictestAdmin.maximumFailedPasswordsForWipe;
                     if (max > 0 && policy.mFailedPasswordAttempts >= max) {
-                        wipeDeviceOrUserLocked(0, userHandle);
+                        // Wipe the user/profile associated with the policy that was violated. This
+                        // is not necessarily calling user: if the policy that fired was from a
+                        // managed profile rather than the main user profile, we wipe former only.
+                        wipeDeviceOrUserLocked(0, strictestAdmin.getUserHandle().getIdentifier());
                     }
                     sendAdminCommandToSelfAndProfilesLocked(
                             DeviceAdminReceiver.ACTION_PASSWORD_FAILED,