Make DPM.isActivePasswordSufficient() synchronous again

At the moment isActivePasswordSufficient() can return stale result
immediately after a password change since the latest password metrics
information is pushed from LSS to DPMS asynchronously (this is to
avoid deadlock between LSS and DPMS due to lock inversion). Fix this
by moving the password metrics ground truth to LSS, and make DPMS
query LSS about the password metrics when needed.

Bug: 37090873
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest
Test: atest com.android.server.locksettings
Test: atest DeviceAdminHostSideTestApi24#testResetPassword_nycRestrictions
Test: atest DeviceAdminHostSideTestApi24#testRunDeviceOwnerPasswordTest
Test: atest MixedManagedProfileOwnerTest#testPasswordQualityWithoutSecureLockScreen
Test: atest MixedManagedProfileOwnerTest#testResetPasswordWithToken
Test: atest MixedManagedProfileOwnerTest#testPasswordSufficientInitially

Change-Id: Ib1d1716024b8a6a6554afdb4ee9824d457ed8308
diff --git a/config/hiddenapi-greylist-max-o.txt b/config/hiddenapi-greylist-max-o.txt
index d9c1cd0..15026b0 100644
--- a/config/hiddenapi-greylist-max-o.txt
+++ b/config/hiddenapi-greylist-max-o.txt
@@ -3040,7 +3040,6 @@
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->retrieveSecurityLogs(Landroid/content/ComponentName;)Landroid/content/pm/ParceledListSlice;
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setAccountManagementDisabled(Landroid/content/ComponentName;Ljava/lang/String;Z)V
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setActiveAdmin(Landroid/content/ComponentName;ZI)V
-Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setActivePasswordState(Landroid/app/admin/PasswordMetrics;I)V
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setAffiliationIds(Landroid/content/ComponentName;Ljava/util/List;)V
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setAlwaysOnVpnPackage(Landroid/content/ComponentName;Ljava/lang/String;Z)Z
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setApplicationHidden(Landroid/content/ComponentName;Ljava/lang/String;Ljava/lang/String;Z)Z
@@ -3305,7 +3304,6 @@
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_retrieveSecurityLogs:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setAccountManagementDisabled:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setActiveAdmin:I
-Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setActivePasswordState:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setAffiliationIds:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setAlwaysOnVpnPackage:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setApplicationHidden:I
@@ -3569,7 +3567,6 @@
 Landroid/app/admin/IDevicePolicyManager;->retrieveSecurityLogs(Landroid/content/ComponentName;)Landroid/content/pm/ParceledListSlice;
 Landroid/app/admin/IDevicePolicyManager;->setAccountManagementDisabled(Landroid/content/ComponentName;Ljava/lang/String;Z)V
 Landroid/app/admin/IDevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;ZI)V
-Landroid/app/admin/IDevicePolicyManager;->setActivePasswordState(Landroid/app/admin/PasswordMetrics;I)V
 Landroid/app/admin/IDevicePolicyManager;->setAffiliationIds(Landroid/content/ComponentName;Ljava/util/List;)V
 Landroid/app/admin/IDevicePolicyManager;->setAlwaysOnVpnPackage(Landroid/content/ComponentName;Ljava/lang/String;Z)Z
 Landroid/app/admin/IDevicePolicyManager;->setApplicationHidden(Landroid/content/ComponentName;Ljava/lang/String;Ljava/lang/String;Z)Z
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff5a043..a136bbd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5764,21 +5764,6 @@
     /**
      * @hide
      */
-    @UnsupportedAppUsage
-    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
-    public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
-        if (mService != null) {
-            try {
-                mService.setActivePasswordState(metrics, userHandle);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportPasswordChanged(@UserIdInt int userId) {
         if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 5cdef6d..0da5b7a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -131,7 +131,6 @@
     void forceRemoveActiveAdmin(in ComponentName policyReceiver, int userHandle);
     boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
 
-    void setActivePasswordState(in PasswordMetrics metrics, int userHandle);
     void reportPasswordChanged(int userId);
     void reportFailedPasswordAttempt(int userHandle);
     void reportSuccessfulPasswordAttempt(int userHandle);
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index d459bfb..aab2f4b 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.widget;
 
+import android.annotation.Nullable;
+import android.app.admin.PasswordMetrics;
 
 /**
  * LockSettingsService local system service interface.
@@ -61,4 +63,18 @@
             long tokenHandle, byte[] token, int requestedQuality, int userId);
 
     public abstract boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId);
+
+    /**
+     * Returns PasswordMetrics object corresponding to the given user's lockscreen password.
+     * If the user has a password but its metrics isn't known yet (for example if the device
+     * has not been unlocked since boot), this method will return {@code null}.
+     * If the user has no password, a default PasswordMetrics (PASSWORD_QUALITY_UNSPECIFIED)
+     * will be returned.
+     *
+     * Calling this method on a managed profile user with unified challenge is undefined.
+     *
+     * @param userHandle the user for whom to provide metrics.
+     * @return the user password metrics.
+     */
+    public abstract @Nullable PasswordMetrics getUserPasswordMetrics(int userHandle);
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f38f2f9..ccc0d4b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -217,6 +217,11 @@
     private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
 
     private boolean mFirstCallToVold;
+    // Current password metric for all users on the device. Updated when user unlocks
+    // the device or changes password. Removed when user is stopped.
+    @GuardedBy("this")
+    final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
+
     protected IGateKeeperService mGateKeeperService;
     protected IAuthSecret mAuthSecretService;
 
@@ -424,6 +429,13 @@
             return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         }
 
+        /**
+         * Return the {@link DevicePolicyManager} object.
+         *
+         * Since LockSettingsService is considered a lower-level component than DevicePolicyManager,
+         * do NOT hold any lock in this class while calling into DevicePolicyManager to prevent
+         * the risk of deadlock.
+         */
         public DevicePolicyManager getDevicePolicyManager() {
             return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         }
@@ -593,6 +605,9 @@
         // the device.
         int strongAuthRequired = LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
         requireStrongAuth(strongAuthRequired, userId);
+        synchronized (this) {
+            mUserPasswordMetrics.remove(userId);
+        }
     }
 
     public void onStartUser(final int userId) {
@@ -1501,7 +1516,7 @@
             setKeystorePassword(null, userId);
             fixateNewestUserKeyAuth(userId);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-            notifyActivePasswordMetricsAvailable(CREDENTIAL_TYPE_NONE, null, userId);
+            setUserPasswordMetrics(CREDENTIAL_TYPE_NONE, null, userId);
             sendCredentialsOnChangeIfRequired(
                     credentialType, credential, userId, isLockTiedToParent);
             return;
@@ -1946,7 +1961,7 @@
                     Log.w(TAG, "progressCallback throws exception", e);
                 }
             }
-            notifyActivePasswordMetricsAvailable(storedHash.type, credential, userId);
+            setUserPasswordMetrics(storedHash.type, credential, userId);
             unlockKeystore(credential, userId);
 
             Slog.i(TAG, "Unlocking user " + userId + " with token length "
@@ -1988,32 +2003,40 @@
     }
 
     /**
-     * Call this method to notify DPMS regarding the latest password metric. This should be called
-     * when the user is authenticating or when a new password is being set.
+     * Keep track of the given user's latest password metric. This should be called
+     * when the user is authenticating or when a new password is being set. In comparison,
+     * {@link #notifyPasswordChanged} only needs to be called when the user changes the password.
      */
-    private void notifyActivePasswordMetricsAvailable(
-            @CredentialType int credentialType, byte[] password, @UserIdInt int userId) {
-        final PasswordMetrics metrics =
-                PasswordMetrics.computeForCredential(credentialType, password);
+    private void setUserPasswordMetrics(@CredentialType int credentialType, byte[] password,
+            @UserIdInt int userHandle) {
+        synchronized (this) {
+            mUserPasswordMetrics.put(userHandle,
+                    PasswordMetrics.computeForCredential(credentialType, password));
+        }
+    }
 
-        // Asynchronous to avoid dead lock
-        mHandler.post(() -> {
-            final DevicePolicyManager dpm = (DevicePolicyManager)
-                    mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-            dpm.setActivePasswordState(metrics, userId);
-        });
+    @VisibleForTesting
+    PasswordMetrics getUserPasswordMetrics(int userHandle) {
+        if (!isUserSecure(userHandle)) {
+            // for users without password, mUserPasswordMetrics might not be initialized
+            // since the user never unlock the device manually. In this case, always
+            // return a default metrics object. This is to distinguish this case from
+            // the case where during boot user password is unknown yet (returning null here)
+            return new PasswordMetrics();
+        }
+        synchronized (this) {
+            return mUserPasswordMetrics.get(userHandle);
+        }
     }
 
     /**
-     * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before
+     * Call after {@link #setUserPasswordMetrics} so metrics are updated before
      * reporting the password changed.
      */
     private void notifyPasswordChanged(@UserIdInt int userId) {
         // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
         mHandler.post(() -> {
-            DevicePolicyManager dpm = (DevicePolicyManager)
-                    mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-            dpm.reportPasswordChanged(userId);
+            mInjector.getDevicePolicyManager().reportPasswordChanged(userId);
             LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId);
         });
     }
@@ -2582,7 +2605,7 @@
         }
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            notifyActivePasswordMetricsAvailable(credentialType, userCredential, userId);
+            setUserPasswordMetrics(credentialType, userCredential, userId);
             unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
 
             // Do resetLockout / revokeChallenge when all profiles are unlocked
@@ -2676,7 +2699,7 @@
         setSyntheticPasswordHandleLocked(newHandle, userId);
         synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
 
-        notifyActivePasswordMetricsAvailable(credentialType, credential, userId);
+        setUserPasswordMetrics(credentialType, credential, userId);
 
         if (profilePasswords != null) {
             for (Map.Entry<Integer, byte[]> entry : profilePasswords.entrySet()) {
@@ -2982,6 +3005,8 @@
             pw.println("hasPassword: " + havePassword(userId));
             pw.println("hasPattern: " + havePattern(userId)); // print raw credential type instead?
             pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabled(userId));
+            pw.println(String.format("metrics: %s",
+                    getUserPasswordMetrics(userId) != null ? "known" : "unknown"));
             pw.decreaseIndent();
         }
         pw.println();
@@ -3158,5 +3183,23 @@
         public boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId) {
             return LockSettingsService.this.unlockUserWithToken(tokenHandle, token, userId);
         }
+
+        @Override
+        public PasswordMetrics getUserPasswordMetrics(int userHandle) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                if (isManagedProfileWithUnifiedLock(userHandle)) {
+                    // A managed profile with unified challenge is supposed to be protected by the
+                    // parent lockscreen, so asking for its password metrics is not really useful,
+                    // as this method would just return the metrics of the random profile password
+                    Slog.w(TAG, "Querying password metrics for unified challenge profile: "
+                            + userHandle);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            return LockSettingsService.this.getUserPasswordMetrics(userHandle);
+        }
+
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f933b09..478bc88 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -248,6 +248,7 @@
 import com.android.internal.util.StatLogger;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemServerInitThreadPool;
@@ -500,6 +501,7 @@
     final UsageStatsManagerInternal mUsageStatsManagerInternal;
     final TelephonyManager mTelephonyManager;
     private final LockPatternUtils mLockPatternUtils;
+    private final LockSettingsInternal mLockSettingsInternal;
     private final DeviceAdminServiceController mDeviceAdminServiceController;
     private final OverlayPackagesProvider mOverlayPackagesProvider;
 
@@ -739,7 +741,6 @@
     final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
 
     @GuardedBy("getLockObject()")
-    final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
 
     final Handler mHandler;
     final Handler mBackgroundHandler;
@@ -1994,6 +1995,10 @@
             return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
         }
 
+        LockSettingsInternal getLockSettingsInternal() {
+            return LocalServices.getService(LockSettingsInternal.class);
+        }
+
         boolean isBuildDebuggable() {
             return Build.IS_DEBUGGABLE;
         }
@@ -2233,7 +2238,7 @@
 
         mLocalService = new LocalService();
         mLockPatternUtils = injector.newLockPatternUtils();
-
+        mLockSettingsInternal = injector.getLockSettingsInternal();
         // TODO: why does SecurityLogMonitor need to be created even when mHasFeature == false?
         mSecurityLogMonitor = new SecurityLogMonitor(this);
 
@@ -2309,17 +2314,6 @@
     }
 
     /**
-     * Provides PasswordMetrics object corresponding to the given user.
-     * @param userHandle the user for whom to provide metrics.
-     * @return the user password metrics, or {@code null} if none have been associated with
-     * the user yet (for example, if the device has booted but not been unlocked).
-     */
-    @GuardedBy("getLockObject()")
-    PasswordMetrics getUserPasswordMetricsLocked(int userHandle) {
-        return mUserPasswordMetrics.get(userHandle);
-    }
-
-    /**
      * Creates and loads the policy data from xml for data that is shared between
      * various profiles of a user. In contrast to {@link #getUserData(int)}
      * it allows access to data of users other than the calling user.
@@ -2353,9 +2347,6 @@
             if (policy != null) {
                 mUserData.remove(userHandle);
             }
-            if (mUserPasswordMetrics.get(userHandle) != null) {
-                mUserPasswordMetrics.remove(userHandle);
-            }
 
             File policyFile = new File(mInjector.environmentGetUserSystemDirectory(userHandle),
                     DEVICE_POLICIES_XML);
@@ -4183,15 +4174,16 @@
     private void updatePasswordValidityCheckpointLocked(int userHandle, boolean parent) {
         final int credentialOwner = getCredentialOwner(userHandle, parent);
         DevicePolicyData policy = getUserData(credentialOwner);
-        PasswordMetrics metrics = getUserPasswordMetricsLocked(credentialOwner);
-        if (metrics == null) {
-            metrics = new PasswordMetrics();
+        PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
+        // Update the checkpoint only if the user's password metrics is known
+        if (metrics != null) {
+            final boolean newCheckpoint = isPasswordSufficientForUserWithoutCheckpointLocked(
+                    metrics, userHandle, parent);
+            if (newCheckpoint != policy.mPasswordValidAtLastCheckpoint) {
+                policy.mPasswordValidAtLastCheckpoint = newCheckpoint;
+                saveSettingsLocked(credentialOwner);
+            }
         }
-        policy.mPasswordValidAtLastCheckpoint =
-                isPasswordSufficientForUserWithoutCheckpointLocked(
-                        metrics, userHandle, parent);
-
-        saveSettingsLocked(credentialOwner);
     }
 
     /**
@@ -4766,7 +4758,7 @@
             getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
             int credentialOwner = getCredentialOwner(userHandle, parent);
             DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
-            PasswordMetrics metrics = getUserPasswordMetricsLocked(credentialOwner);
+            PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
             return isActivePasswordSufficientForUserLocked(
                     policy.mPasswordValidAtLastCheckpoint, metrics, userHandle, parent);
         }
@@ -4796,15 +4788,15 @@
             enforceUserUnlocked(targetUser, false);
             int credentialOwner = getCredentialOwner(userHandle, false);
             DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
-            PasswordMetrics metrics = getUserPasswordMetricsLocked(credentialOwner);
+            PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
             return isActivePasswordSufficientForUserLocked(
                     policy.mPasswordValidAtLastCheckpoint, metrics, targetUser, false);
         }
     }
 
     private boolean isActivePasswordSufficientForUserLocked(
-            boolean passwordValidAtLastCheckpoint, PasswordMetrics metrics, int userHandle,
-            boolean parent) {
+            boolean passwordValidAtLastCheckpoint, @Nullable PasswordMetrics metrics,
+            int userHandle, boolean parent) {
         if (!mInjector.storageManagerIsFileBasedEncryptionEnabled() && (metrics == null)) {
             // Before user enters their password for the first time after a reboot, return the
             // value of this flag, which tells us whether the password was valid the last time
@@ -4815,9 +4807,10 @@
         }
 
         if (metrics == null) {
-            // This could happen if the user never had a password set, for example, so
-            // setActivePasswordState has never been called for it.
-            metrics = new PasswordMetrics();
+            // Called on a FBE device when the user password exists but its metrics is unknown.
+            // This shouldn't happen since we enforce the user to be unlocked (which would result
+            // in the metrics known to the framework on a FBE device) at all call sites.
+            throw new IllegalStateException("isActivePasswordSufficient called on FBE-locked user");
         }
 
         return isPasswordSufficientForUserWithoutCheckpointLocked(metrics, userHandle, parent);
@@ -4829,7 +4822,7 @@
      * {@code userId} (or its parent, if {@code parent} is set to {@code true}).
      */
     private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
-            PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
+            @NonNull PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
         final int requiredQuality = getPasswordQuality(null, userId, parent);
 
         if (requiredQuality >= PASSWORD_QUALITY_NUMERIC
@@ -4867,7 +4860,7 @@
 
         synchronized (getLockObject()) {
             int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false);
-            PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId);
+            PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(targetUserId);
             return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity();
         }
     }
@@ -6707,31 +6700,6 @@
         }
     }
 
-    /**
-     * Notify DPMS regarding the metric of the current password. This happens when the user changes
-     * the password, but also when the user just unlocks the keyguard. In comparison,
-     * reportPasswordChanged() is only called when the user changes the password.
-     */
-    @Override
-    public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
-        if (!mLockPatternUtils.hasSecureLockScreen()) {
-            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)) {
-            metrics = new PasswordMetrics();
-        }
-
-        validateQualityConstant(metrics.quality);
-        synchronized (getLockObject()) {
-            mUserPasswordMetrics.put(userHandle, metrics);
-        }
-    }
-
     @Override
     public void reportPasswordChanged(@UserIdInt int userId) {
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ce83df7..64ea59d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 
 import java.io.File;
@@ -210,6 +211,11 @@
         }
 
         @Override
+        LockSettingsInternal getLockSettingsInternal() {
+            return services.lockSettingsInternal;
+        }
+
+        @Override
         IAudioService getIAudioService() {
             return services.iaudioService;
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 9ae9824..d6cb9826 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -764,7 +765,7 @@
     }
 
     /**
-     * Test for: @{link DevicePolicyManager#setActivePasswordState}
+     * Test for: @{link DevicePolicyManager#reportPasswordChanged}
      *
      * Validates that when the password for a user changes, the notification broadcast intent
      * {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is sent to managed profile owners, in
@@ -806,7 +807,7 @@
     }
 
     /**
-     * Test for: @{link DevicePolicyManager#setActivePasswordState}
+     * Test for: @{link DevicePolicyManager#reportPasswordChanged}
      *
      * Validates that when the password for a managed profile changes, the notification broadcast
      * intent {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is only sent to the profile, not
@@ -4258,6 +4259,10 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         mContext.packageName = admin1.getPackageName();
         setupDeviceOwner();
+        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+        // When there is no lockscreen, user password metrics is always empty.
+        when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle))
+                .thenReturn(new PasswordMetrics());
 
         // If no password requirements are set, isActivePasswordSufficient should succeed.
         assertTrue(dpm.isActivePasswordSufficient());
@@ -4266,14 +4271,7 @@
         dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
 
         reset(mContext.spiedContext);
-        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
-        PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
-                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
-                8, 2,
-                6, 1,
-                0, 1);
         // This should be ignored, as there is no lock screen.
-        dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle);
         dpm.reportPasswordChanged(userHandle);
 
         // No broadcast should be sent.
@@ -4290,19 +4288,24 @@
         final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
         final long ident = mContext.binder.clearCallingIdentity();
 
-        dpm.setActivePasswordState(passwordMetrics, userHandle);
+        when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle))
+                .thenReturn(passwordMetrics);
         dpm.reportPasswordChanged(userHandle);
 
         // Drain ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcasts as part of
         // reportPasswordChanged()
-        // This broadcast should be sent 4 times:
+        // This broadcast should be sent 2-4 times:
         // * Twice from calls to DevicePolicyManagerService.updatePasswordExpirationsLocked,
         //   once for each affected user, in DevicePolicyManagerService.reportPasswordChanged.
-        // * Twice from calls to DevicePolicyManagerService.saveSettingsLocked
+        // * Optionally, at most twice from calls to DevicePolicyManagerService.saveSettingsLocked
         //   in DevicePolicyManagerService.reportPasswordChanged, once with the userId
         //   the password change is relevant to and another with the credential owner of said
-        //   userId.
-        verify(mContext.spiedContext, times(4)).sendBroadcastAsUser(
+        //   userId, if the password checkpoint value changes.
+        verify(mContext.spiedContext, atMost(4)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(userHandle));
+        verify(mContext.spiedContext, atLeast(2)).sendBroadcastAsUser(
                 MockUtils.checkIntentAction(
                         DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
                 MockUtils.checkUserHandle(userHandle));
@@ -5224,9 +5227,9 @@
         mServiceContext.permissions.add(permission.REQUEST_PASSWORD_COMPLEXITY);
         when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
                 .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
-        dpms.mUserPasswordMetrics.put(
-                DpmMockContext.CALLER_USER_HANDLE,
-                PasswordMetrics.computeForPassword("asdf".getBytes()));
+        when(getServices().lockSettingsInternal
+                .getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
 
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
     }
@@ -5241,12 +5244,12 @@
         when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
                 .thenReturn(parentUser.id);
 
-        dpms.mUserPasswordMetrics.put(
-                DpmMockContext.CALLER_USER_HANDLE,
-                PasswordMetrics.computeForPassword("asdf".getBytes()));
-        dpms.mUserPasswordMetrics.put(
-                parentUser.id,
-                PasswordMetrics.computeForPassword("parentUser".getBytes()));
+        when(getServices().lockSettingsInternal
+                .getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+        when(getServices().lockSettingsInternal
+                .getUserPasswordMetrics(parentUser.id))
+                .thenReturn(PasswordMetrics.computeForPassword("parentUser".getBytes()));
 
         assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index f6f365e..b0d0303 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -64,6 +64,7 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -102,6 +103,7 @@
     public final IBackupManager ibackupManager;
     public final IAudioService iaudioService;
     public final LockPatternUtils lockPatternUtils;
+    public final LockSettingsInternal lockSettingsInternal;
     public final StorageManagerForMock storageManager;
     public final WifiManager wifiManager;
     public final SettingsForMock settings;
@@ -143,6 +145,7 @@
         ibackupManager = mock(IBackupManager.class);
         iaudioService = mock(IAudioService.class);
         lockPatternUtils = mock(LockPatternUtils.class);
+        lockSettingsInternal = mock(LockSettingsInternal.class);
         storageManager = mock(StorageManagerForMock.class);
         wifiManager = mock(WifiManager.class);
         settings = mock(SettingsForMock.class);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 1cd590c..341b8f0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -341,6 +341,7 @@
         final byte[] pattern = "123654".getBytes();
         final byte[] token = "some-high-entropy-secure-token".getBytes();
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        reset(mDevicePolicyManager);
         final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
@@ -360,7 +361,8 @@
         flushHandlerTasks();
         final PasswordMetrics metric = PasswordMetrics.computeForCredential(
                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN, pattern);
-        verify(mDevicePolicyManager).setActivePasswordState(metric, PRIMARY_USER_ID);
+        assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
+        verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)