Don't save password metrics to disk.

On FBE devices, don't save the metrics to disk but compute them when the
password is first entered and only store them in RAM.

Merged-in: 5daf273b7e3272269c53eda20ce494d0e7a365b5
Bug: 32793550
Change-Id: Icee7f615167761177b224b342970a36c7d90f6ba
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5305473..78f0c92 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1981,7 +1981,8 @@
      * Determine whether the current password the user has set is sufficient to meet the policy
      * requirements (e.g. quality, minimum length) that have been requested by the admins of this
      * user and its participating profiles. Restrictions on profiles that have a separate challenge
-     * are not taken into account.
+     * are not taken into account. If the user has a password, it must have been entered in order to
+     * perform this check.
      * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
@@ -1994,6 +1995,7 @@
      * @return Returns true if the password meets the current requirements, else false.
      * @throws SecurityException if the calling application does not own an active administrator
      *             that uses {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD}
+     * @throws IllegalStateException if the user has a password but has not entered it yet.
      */
     public boolean isActivePasswordSufficient() {
         if (mService != null) {
@@ -3428,6 +3430,19 @@
     /**
      * @hide
      */
+    public void reportPasswordChanged(int userId) {
+        if (mService != null) {
+            try {
+                mService.reportPasswordChanged(userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
     public void reportFailedPasswordAttempt(int userHandle) {
         if (mService != null) {
             try {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ddec412..2a679eb 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -116,6 +116,7 @@
 
     void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase,
         int numbers, int symbols, int nonletter, int userHandle);
+    void reportPasswordChanged(int userId);
     void reportFailedPasswordAttempt(int userHandle);
     void reportSuccessfulPasswordAttempt(int userHandle);
     void reportFailedFingerprintAttempt(int userHandle);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 7f2f740..d79a5a4 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -565,9 +565,6 @@
             setCredentialRequiredToDecrypt(false);
         }
 
-        getDevicePolicyManager().setActivePasswordState(
-                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle);
-
         onAfterChangingPassword(userHandle);
     }
 
@@ -614,6 +611,7 @@
                         + MIN_LOCK_PATTERN_SIZE + " dots long.");
             }
 
+            setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
             getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId);
             DevicePolicyManager dpm = getDevicePolicyManager();
 
@@ -630,9 +628,6 @@
 
             setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
 
-            setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
-            dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
-                    pattern.size(), 0, 0, 0, 0, 0, 0, userId);
             onAfterChangingPassword(userId);
         } catch (RemoteException re) {
             Log.e(TAG, "Couldn't save lock pattern " + re);
@@ -835,9 +830,9 @@
                         + "of length " + MIN_LOCK_PASSWORD_SIZE);
             }
 
+            final int computedQuality = computePasswordQuality(password);
+            setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
             getLockSettings().setLockPassword(password, savedPassword, userHandle);
-            getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null);
-            int computedQuality = computePasswordQuality(password);
 
             // Update the device encryption password.
             if (userHandle == UserHandle.USER_SYSTEM
@@ -855,40 +850,6 @@
                 }
             }
 
-            setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
-            if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                int letters = 0;
-                int uppercase = 0;
-                int lowercase = 0;
-                int numbers = 0;
-                int symbols = 0;
-                int nonletter = 0;
-                for (int i = 0; i < password.length(); i++) {
-                    char c = password.charAt(i);
-                    if (c >= 'A' && c <= 'Z') {
-                        letters++;
-                        uppercase++;
-                    } else if (c >= 'a' && c <= 'z') {
-                        letters++;
-                        lowercase++;
-                    } else if (c >= '0' && c <= '9') {
-                        numbers++;
-                        nonletter++;
-                    } else {
-                        symbols++;
-                        nonletter++;
-                    }
-                }
-                dpm.setActivePasswordState(Math.max(quality, computedQuality),
-                        password.length(), letters, uppercase, lowercase,
-                        numbers, symbols, nonletter, userHandle);
-            } else {
-                // The password is not anything.
-                dpm.setActivePasswordState(
-                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
-                        0, 0, 0, 0, 0, 0, 0, userHandle);
-            }
-
             // Add the password to the password history. We assume all
             // password hashes have the same length for simplicity of implementation.
             String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 42e75c6..80088b7 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -899,6 +899,7 @@
         synchronized (mSeparateChallengeLock) {
             setLockPatternInternal(pattern, savedCredential, userId);
             setSeparateProfileChallengeEnabled(userId, true, null);
+            notifyPasswordChanged(userId);
         }
     }
 
@@ -913,6 +914,7 @@
             setKeystorePassword(null, userId);
             fixateNewestUserKeyAuth(userId);
             onUserLockChanged(userId);
+            notifyActivePasswordMetricsAvailable(null, userId);
             return;
         }
 
@@ -962,6 +964,7 @@
         synchronized (mSeparateChallengeLock) {
             setLockPasswordInternal(password, savedCredential, userId);
             setSeparateProfileChallengeEnabled(userId, true, null);
+            notifyPasswordChanged(userId);
         }
     }
 
@@ -975,6 +978,7 @@
             setKeystorePassword(null, userId);
             fixateNewestUserKeyAuth(userId);
             onUserLockChanged(userId);
+            notifyActivePasswordMetricsAvailable(null, userId);
             return;
         }
 
@@ -1365,6 +1369,7 @@
                 // migrate credential to GateKeeper
                 credentialUtil.setCredential(credential, null, userId);
                 if (!hasChallenge) {
+                    notifyActivePasswordMetricsAvailable(credential, userId);
                     return VerifyCredentialResponse.OK;
                 }
                 // Fall through to get the auth token. Technically this should never happen,
@@ -1398,6 +1403,7 @@
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
             // credential has matched
+            notifyActivePasswordMetricsAvailable(credential, userId);
             unlockKeystore(credential, userId);
 
             Slog.i(TAG, "Unlocking user " + userId +
@@ -1421,6 +1427,60 @@
         return response;
     }
 
+    private void notifyActivePasswordMetricsAvailable(final String password, int userId) {
+        final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId);
+
+        // Asynchronous to avoid dead lock
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                int length = 0;
+                int letters = 0;
+                int uppercase = 0;
+                int lowercase = 0;
+                int numbers = 0;
+                int symbols = 0;
+                int nonletter = 0;
+                if (password != null) {
+                    length = password.length();
+                    for (int i = 0; i < length; i++) {
+                        char c = password.charAt(i);
+                        if (c >= 'A' && c <= 'Z') {
+                            letters++;
+                            uppercase++;
+                        } else if (c >= 'a' && c <= 'z') {
+                            letters++;
+                            lowercase++;
+                        } else if (c >= '0' && c <= '9') {
+                            numbers++;
+                            nonletter++;
+                        } else {
+                            symbols++;
+                            nonletter++;
+                        }
+                    }
+                }
+                DevicePolicyManager dpm = (DevicePolicyManager)
+                        mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+                dpm.setActivePasswordState(quality, length, letters, uppercase, lowercase, numbers,
+                        symbols, nonletter, userId);
+            }
+        });
+    }
+
+    /**
+     * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before
+     * reporting the password changed.
+     */
+    private void notifyPasswordChanged(int userId) {
+        // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
+        mHandler.post(() -> {
+            DevicePolicyManager dpm = (DevicePolicyManager)
+                    mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+            dpm.reportPasswordChanged(userId);
+        });
+    }
+
     @Override
     public boolean checkVoldPassword(int userId) throws RemoteException {
         if (!mFirstCallToVold) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2aaf945..80dfe66 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2218,10 +2218,13 @@
                 out.endTag(null, "failed-password-attempts");
             }
 
-            if (policy.mActivePasswordQuality != 0 || policy.mActivePasswordLength != 0
+            // Don't save metrics for FBE devices
+            if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()
+                    && (policy.mActivePasswordQuality != 0 || policy.mActivePasswordLength != 0
                     || policy.mActivePasswordUpperCase != 0 || policy.mActivePasswordLowerCase != 0
                     || policy.mActivePasswordLetters != 0 || policy.mActivePasswordNumeric != 0
-                    || policy.mActivePasswordSymbols != 0 || policy.mActivePasswordNonLetter != 0) {
+                    || policy.mActivePasswordSymbols != 0
+                    || policy.mActivePasswordNonLetter != 0)) {
                 out.startTag(null, "active-password");
                 out.attribute(null, "quality", Integer.toString(policy.mActivePasswordQuality));
                 out.attribute(null, "length", Integer.toString(policy.mActivePasswordLength));
@@ -2314,6 +2317,7 @@
         JournaledFile journal = makeJournaledFile(userHandle);
         FileInputStream stream = null;
         File file = journal.chooseForRead();
+        boolean needsRewrite = false;
         try {
             stream = new FileInputStream(file);
             XmlPullParser parser = Xml.newPullParser();
@@ -2390,23 +2394,6 @@
                 } else if ("password-owner".equals(tag)) {
                     policy.mPasswordOwner = Integer.parseInt(
                             parser.getAttributeValue(null, "value"));
-                } else if ("active-password".equals(tag)) {
-                    policy.mActivePasswordQuality = Integer.parseInt(
-                            parser.getAttributeValue(null, "quality"));
-                    policy.mActivePasswordLength = Integer.parseInt(
-                            parser.getAttributeValue(null, "length"));
-                    policy.mActivePasswordUpperCase = Integer.parseInt(
-                            parser.getAttributeValue(null, "uppercase"));
-                    policy.mActivePasswordLowerCase = Integer.parseInt(
-                            parser.getAttributeValue(null, "lowercase"));
-                    policy.mActivePasswordLetters = Integer.parseInt(
-                            parser.getAttributeValue(null, "letters"));
-                    policy.mActivePasswordNumeric = Integer.parseInt(
-                            parser.getAttributeValue(null, "numeric"));
-                    policy.mActivePasswordSymbols = Integer.parseInt(
-                            parser.getAttributeValue(null, "symbols"));
-                    policy.mActivePasswordNonLetter = Integer.parseInt(
-                            parser.getAttributeValue(null, "nonletter"));
                 } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
                     policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
                 } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
@@ -2423,6 +2410,28 @@
                     policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending);
                 } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) {
                     policy.mInitBundle = PersistableBundle.restoreFromXml(parser);
+                } else if ("active-password".equals(tag)) {
+                    if (mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
+                        // Remove this from FBE devices
+                        needsRewrite = true;
+                    } else {
+                        policy.mActivePasswordQuality = Integer.parseInt(
+                            parser.getAttributeValue(null, "quality"));
+                        policy.mActivePasswordLength = Integer.parseInt(
+                                parser.getAttributeValue(null, "length"));
+                        policy.mActivePasswordUpperCase = Integer.parseInt(
+                                parser.getAttributeValue(null, "uppercase"));
+                        policy.mActivePasswordLowerCase = Integer.parseInt(
+                                parser.getAttributeValue(null, "lowercase"));
+                        policy.mActivePasswordLetters = Integer.parseInt(
+                                parser.getAttributeValue(null, "letters"));
+                        policy.mActivePasswordNumeric = Integer.parseInt(
+                                parser.getAttributeValue(null, "numeric"));
+                        policy.mActivePasswordSymbols = Integer.parseInt(
+                                parser.getAttributeValue(null, "symbols"));
+                        policy.mActivePasswordNonLetter = Integer.parseInt(
+                                parser.getAttributeValue(null, "nonletter"));
+                    }
                 } else {
                     Slog.w(LOG_TAG, "Unknown tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -2442,34 +2451,14 @@
             // Ignore
         }
 
+        // Might need to upgrade the file by rewriting it
+        if (needsRewrite) {
+            saveSettingsLocked(userHandle);
+        }
+
         // Generate a list of admins from the admin map
         policy.mAdminList.addAll(policy.mAdminMap.values());
 
-        // Validate that what we stored for the password quality matches
-        // sufficiently what is currently set.  Note that this is only
-        // a sanity check in case the two get out of sync; this should
-        // never normally happen.
-        final long identity = mInjector.binderClearCallingIdentity();
-        try {
-            int actualPasswordQuality = mLockPatternUtils.getActivePasswordQuality(userHandle);
-            if (actualPasswordQuality < policy.mActivePasswordQuality) {
-                Slog.w(LOG_TAG, "Active password quality 0x"
-                        + Integer.toHexString(policy.mActivePasswordQuality)
-                        + " does not match actual quality 0x"
-                        + Integer.toHexString(actualPasswordQuality));
-                policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-                policy.mActivePasswordLength = 0;
-                policy.mActivePasswordUpperCase = 0;
-                policy.mActivePasswordLowerCase = 0;
-                policy.mActivePasswordLetters = 0;
-                policy.mActivePasswordNumeric = 0;
-                policy.mActivePasswordSymbols = 0;
-                policy.mActivePasswordNonLetter = 0;
-            }
-        } finally {
-            mInjector.binderRestoreCallingIdentity(identity);
-        }
-
         validatePasswordOwnerLocked(policy);
         updateMaximumTimeToLockLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
@@ -3663,6 +3652,8 @@
 
     private boolean isActivePasswordSufficientForUserLocked(
             DevicePolicyData policy, int userHandle, boolean parent) {
+        enforceUserUnlocked(userHandle, parent);
+
         if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle, parent)
                 || policy.mActivePasswordLength < getPasswordMinimumLength(
                         null, userHandle, parent)) {
@@ -4673,40 +4664,66 @@
             return;
         }
         enforceFullCrossUsersPermission(userHandle);
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+
+        // If the managed profile doesn't have a separate password, set the metrics to default
+        if (isManagedProfile(userHandle) && !isSeparateProfileChallengeEnabled(userHandle)) {
+            quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+            length = 0;
+            letters = 0;
+            uppercase = 0;
+            lowercase = 0;
+            numbers = 0;
+            symbols = 0;
+            nonletter = 0;
+        }
+
+        validateQualityConstant(quality);
+        DevicePolicyData policy = getUserData(userHandle);
+        synchronized (this) {
+            policy.mActivePasswordQuality = quality;
+            policy.mActivePasswordLength = length;
+            policy.mActivePasswordLetters = letters;
+            policy.mActivePasswordLowerCase = lowercase;
+            policy.mActivePasswordUpperCase = uppercase;
+            policy.mActivePasswordNumeric = numbers;
+            policy.mActivePasswordSymbols = symbols;
+            policy.mActivePasswordNonLetter = nonletter;
+        }
+    }
+
+    @Override
+    public void reportPasswordChanged(int userId) {
+        if (!mHasFeature) {
+            return;
+        }
+        enforceFullCrossUsersPermission(userId);
 
         // Managed Profile password can only be changed when it has a separate challenge.
-        if (!isSeparateProfileChallengeEnabled(userHandle)) {
-            enforceNotManagedProfile(userHandle, "set the active password");
+        if (!isSeparateProfileChallengeEnabled(userId)) {
+            enforceNotManagedProfile(userId, "set the active password");
         }
 
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        validateQualityConstant(quality);
 
-        DevicePolicyData policy = getUserData(userHandle);
+        DevicePolicyData policy = getUserData(userId);
 
         long ident = mInjector.binderClearCallingIdentity();
         try {
             synchronized (this) {
-                policy.mActivePasswordQuality = quality;
-                policy.mActivePasswordLength = length;
-                policy.mActivePasswordLetters = letters;
-                policy.mActivePasswordLowerCase = lowercase;
-                policy.mActivePasswordUpperCase = uppercase;
-                policy.mActivePasswordNumeric = numbers;
-                policy.mActivePasswordSymbols = symbols;
-                policy.mActivePasswordNonLetter = nonletter;
                 policy.mFailedPasswordAttempts = 0;
-                saveSettingsLocked(userHandle);
-                updatePasswordExpirationsLocked(userHandle);
-                setExpirationAlarmCheckLocked(mContext, userHandle, /* parent */ false);
+                saveSettingsLocked(userId);
+                updatePasswordExpirationsLocked(userId);
+                setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false);
 
                 // Send a broadcast to each profile using this password as its primary unlock.
                 sendAdminCommandForLockscreenPoliciesLocked(
                         DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
-                        DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle);
+                        DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userId);
             }
-            removeCaApprovalsIfNeeded(userHandle);
+            removeCaApprovalsIfNeeded(userId);
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
@@ -6278,6 +6295,14 @@
                 "User must be running and unlocked");
     }
 
+    private void enforceUserUnlocked(int userId, boolean parent) {
+        if (parent) {
+            enforceUserUnlocked(getProfileParentId(userId));
+        } else {
+            enforceUserUnlocked(userId);
+        }
+    }
+
     private void enforceManageUsers() {
         final int callingUid = mInjector.binderGetCallingUid();
         if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {