Credential FRP: Add implementation

- Adds a facility to store a credential handle that survives factory reset
- Adds a method to KeyguardManager for verifying the stored credential for SetupWizard
- Dark launches persisting the primary user's credential as the FRP credential (behind a default-off flag)

Future work:
- Use a separate GK handle / synthetic password for the FRP credential
- Enroll the FRP credential in verifyCredential for the upgrade case

Bug: 36814845
Test: runtest -x core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java && runtest -x services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java && runtest -x services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
Change-Id: Ia739408c5ecb169e5f09670cd9ceaa7febc2b1cc
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 7de46d9..84cca0e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -96,7 +96,7 @@
             storageDir.mkdirs();
         }
 
-        mSpManager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
+        mSpManager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService, mUserManager);
         mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
                 mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager,
                 mSpManager);
@@ -164,4 +164,3 @@
         assertFalse(Arrays.equals(expected, actual));
     }
 }
-
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 2f0ac38..cb32492 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,11 @@
 
 package com.android.server.locksettings;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -44,21 +49,23 @@
     }
 
     public void testCreatePasswordPrimaryUser() throws RemoteException {
-        testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD);
+        testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD,
+                PASSWORD_QUALITY_ALPHABETIC);
     }
 
     public void testCreatePatternPrimaryUser() throws RemoteException {
-        testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN);
+        testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
+                PASSWORD_QUALITY_SOMETHING);
     }
 
     public void testChangePasswordPrimaryUser() throws RemoteException {
         testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
-                "asdfghjk", CREDENTIAL_TYPE_PASSWORD);
+                "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
     }
 
     public void testChangePatternPrimaryUser() throws RemoteException {
         testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
-                "1596321", CREDENTIAL_TYPE_PATTERN);
+                "1596321", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
     }
 
     public void testChangePasswordFailPrimaryUser() throws RemoteException {
@@ -68,13 +75,14 @@
 
         try {
             mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
-                    PRIMARY_USER_ID);
+                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
             fail("Did not fail when enrolling using incorrect credential");
         } catch (RemoteException expected) {
             assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
         }
         try {
-            mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+            mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null,
+                    PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
             fail("Did not fail when enrolling using incorrect credential");
         } catch (RemoteException expected) {
             assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
@@ -85,7 +93,8 @@
     public void testClearPasswordPrimaryUser() throws RemoteException {
         final String PASSWORD = "password";
         initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
-        mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID);
+        mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD,
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
         assertFalse(mService.havePassword(PRIMARY_USER_ID));
         assertFalse(mService.havePattern(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -94,7 +103,7 @@
     public void testManagedProfileUnifiedChallenge() throws RemoteException {
         final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
         mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PRIMARY_USER_ID);
+                PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -129,14 +138,14 @@
         mStorageManager.setIgnoreBadUnlock(true);
         // Change primary password and verify that profile SID remains
         mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                UnifiedPassword, PRIMARY_USER_ID);
+                UnifiedPassword, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
         // Clear unified challenge
         mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword,
-                PRIMARY_USER_ID);
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
@@ -146,14 +155,14 @@
         final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
         final String profilePassword = "testManagedProfileSeparateChallenge-profile";
         mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PRIMARY_USER_ID);
+                PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID);
         /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
          * credential as part of verifyCredential() before the new credential is committed in
          * StorageManager. So we relax the check in our mock StorageManager to allow that.
          */
         mStorageManager.setIgnoreBadUnlock(true);
         mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                MANAGED_PROFILE_USER_ID);
+                PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID);
         mStorageManager.setIgnoreBadUnlock(false);
 
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
@@ -180,7 +189,7 @@
         // Change primary credential and make sure we don't affect profile
         mStorageManager.setIgnoreBadUnlock(true);
         mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                primaryPassword, PRIMARY_USER_ID);
+                primaryPassword, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
@@ -188,17 +197,17 @@
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
     }
 
-    private void testCreateCredential(int userId, String credential, int type)
+    private void testCreateCredential(int userId, String credential, int type, int quality)
             throws RemoteException {
-        mService.setLockCredential(credential, type, null, userId);
+        mService.setLockCredential(credential, type, null, quality, userId);
         assertVerifyCredentials(userId, credential, type, -1);
     }
 
     private void testChangeCredentials(int userId, String newCredential, int newType,
-            String oldCredential, int oldType) throws RemoteException {
+            String oldCredential, int oldType, int quality) throws RemoteException {
         final long sid = 1234;
         initializeStorageWithCredential(userId, oldCredential, oldType, sid);
-        mService.setLockCredential(newCredential, newType, oldCredential, userId);
+        mService.setLockCredential(newCredential, newType, oldCredential, quality, userId);
         assertVerifyCredentials(userId, newCredential, newType, sid);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 449a54c..a0578c9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -22,8 +22,6 @@
 
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.FileUtils;
@@ -33,6 +31,8 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -43,11 +43,14 @@
  * runtest frameworks-services -c com.android.server.locksettings.LockSettingsStorageTests
  */
 public class LockSettingsStorageTests extends AndroidTestCase {
+    private static final int SOME_USER_ID = 1034;
     private final byte[] PASSWORD_0 = "thepassword0".getBytes();
     private final byte[] PASSWORD_1 = "password1".getBytes();
     private final byte[] PATTERN_0 = "123654".getBytes();
     private final byte[] PATTERN_1 = "147852369".getBytes();
 
+    public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
+
     LockSettingsStorage mStorage;
     File mStorageDir;
 
@@ -342,6 +345,83 @@
         assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
     }
 
+    public void testPersistentData_serializeUnserialize() {
+        byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_GATEKEEPER, SOME_USER_ID,
+                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
+        PersistentData deserialized = PersistentData.fromBytes(serialized);
+
+        assertEquals(PersistentData.TYPE_GATEKEEPER, deserialized.type);
+        assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, deserialized.qualityForUi);
+        assertArrayEquals(PAYLOAD, deserialized.payload);
+    }
+
+    public void testPersistentData_unserializeNull() {
+        PersistentData deserialized = PersistentData.fromBytes(null);
+        assertSame(PersistentData.NONE, deserialized);
+    }
+
+    public void testPersistentData_unserializeEmptyArray() {
+        PersistentData deserialized = PersistentData.fromBytes(new byte[0]);
+        assertSame(PersistentData.NONE, deserialized);
+    }
+
+    public void testPersistentData_unserialize_version1() {
+        // This test ensures that we can read serialized VERSION_1 PersistentData even if we change
+        // the wire format in the future.
+        byte[] serializedVersion1 = new byte[] {
+                1, /* PersistentData.VERSION_1 */
+                2, /* PersistentData.TYPE_SP */
+                0x00, 0x00, 0x04, 0x0A,  /* SOME_USER_ID */
+                0x00, 0x03, 0x00, 0x00,  /* PASSWORD_NUMERIC_COMPLEX */
+                1, 2, -1, -2, 33, /* PAYLOAD */
+        };
+        PersistentData deserialized = PersistentData.fromBytes(serializedVersion1);
+        assertEquals(PersistentData.TYPE_SP, deserialized.type);
+        assertEquals(SOME_USER_ID, deserialized.userId);
+        assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
+                deserialized.qualityForUi);
+        assertArrayEquals(PAYLOAD, deserialized.payload);
+
+        // Make sure the constants we use on the wire do not change.
+        assertEquals(0, PersistentData.TYPE_NONE);
+        assertEquals(1, PersistentData.TYPE_GATEKEEPER);
+        assertEquals(2, PersistentData.TYPE_SP);
+        assertEquals(3, PersistentData.TYPE_SP_WEAVER);
+    }
+
+    public void testCredentialHash_serializeUnserialize() {
+        byte[] serialized = CredentialHash.create(
+                PAYLOAD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD).toBytes();
+        CredentialHash deserialized = CredentialHash.fromBytes(serialized);
+
+        assertEquals(CredentialHash.VERSION_GATEKEEPER, deserialized.version);
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
+        assertArrayEquals(PAYLOAD, deserialized.hash);
+        assertFalse(deserialized.isBaseZeroPattern);
+    }
+
+    public void testCredentialHash_unserialize_versionGatekeeper() {
+        // This test ensures that we can read serialized VERSION_GATEKEEPER CredentialHashes
+        // even if we change the wire format in the future.
+        byte[] serialized = new byte[] {
+                1, /* VERSION_GATEKEEPER */
+                2, /* CREDENTIAL_TYPE_PASSWORD */
+                0, 0, 0, 5, /* hash length */
+                1, 2, -1, -2, 33, /* hash */
+        };
+        CredentialHash deserialized = CredentialHash.fromBytes(serialized);
+
+        assertEquals(CredentialHash.VERSION_GATEKEEPER, deserialized.version);
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
+        assertArrayEquals(PAYLOAD, deserialized.hash);
+        assertFalse(deserialized.isBaseZeroPattern);
+
+        // Make sure the constants we use on the wire do not change.
+        assertEquals(-1, LockPatternUtils.CREDENTIAL_TYPE_NONE);
+        assertEquals(1, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
+        assertEquals(2, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+    }
+
     private static void assertArrayEquals(byte[] expected, byte[] actual) {
         if (!Arrays.equals(expected, actual)) {
             fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
index eefd361..b89c1d1 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
@@ -173,6 +173,10 @@
     }
 
     @Override
+    public void reportDeviceSetupComplete() throws RemoteException {
+    }
+
+    @Override
     public long getSecureUserId(int userId) throws RemoteException {
         if (sidMap.containsKey(userId)) {
             return sidMap.get(userId);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index ddef5dc..d7468c2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -17,6 +17,7 @@
 
 import android.hardware.weaver.V1_0.IWeaver;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.util.ArrayMap;
 
 import junit.framework.AssertionFailedError;
@@ -35,8 +36,8 @@
     private IWeaver mWeaverService;
 
     public MockSyntheticPasswordManager(LockSettingsStorage storage,
-            MockGateKeeperService gatekeeper) {
-        super(storage);
+            MockGateKeeperService gatekeeper, UserManager userManager) {
+        super(storage, userManager);
         mGateKeeper = gatekeeper;
     }
 
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 0d35385..ba4ff33 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -16,9 +16,15 @@
 
 package com.android.server.locksettings;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
 
+import android.app.admin.DevicePolicyManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
 
@@ -26,6 +32,7 @@
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
 
 
 /**
@@ -33,6 +40,9 @@
  */
 public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
 
+    public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 55};
+    public static final byte[] PAYLOAD2 = new byte[] {2, 3, -2, -3, 44, 1};
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -47,11 +57,13 @@
         final int USER_ID = 10;
         final String PASSWORD = "user-password";
         final String BADPASSWORD = "bad-password";
-        MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
+        MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage,
+                mGateKeeperService, mUserManager);
         AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
                 null, USER_ID);
         long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService, PASSWORD,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, USER_ID);
+                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, PASSWORD_QUALITY_ALPHABETIC,
+                USER_ID);
 
         AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, PASSWORD, USER_ID);
         assertEquals(result.authToken.deriveKeyStorePassword(), authToken.deriveKeyStorePassword());
@@ -76,7 +88,8 @@
         final String PASSWORD = "testPasswordMigration-password";
 
         disableSyntheticPassword(PRIMARY_USER_ID);
-        mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+        mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
         enableSyntheticPassword(PRIMARY_USER_ID);
@@ -94,7 +107,11 @@
 
     private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
         enableSyntheticPassword(userId);
-        mService.setLockCredential(password, password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD : LockPatternUtils.CREDENTIAL_TYPE_NONE, null, userId);
+        int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC
+                : PASSWORD_QUALITY_UNSPECIFIED;
+        int type = password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                : LockPatternUtils.CREDENTIAL_TYPE_NONE;
+        mService.setLockCredential(password, type, null, quality, userId);
     }
 
     public void testSyntheticPasswordChangeCredential() throws RemoteException {
@@ -103,7 +120,8 @@
 
         initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
-        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
                 mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
@@ -129,11 +147,13 @@
         initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // clear password
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, PASSWORD,
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
         assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
         // set a new password
-        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
                 mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
         assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -146,11 +166,13 @@
         initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // clear password
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
         assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
         // set a new password
-        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
                 mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
         assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -163,7 +185,8 @@
         initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // Untrusted change password
-        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
@@ -177,7 +200,8 @@
         final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
         disableSyntheticPassword(PRIMARY_USER_ID);
         disableSyntheticPassword(MANAGED_PROFILE_USER_ID);
-        mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+        mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -207,8 +231,10 @@
     public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
         final String primaryPassword = "testManagedProfileSeparateChallengeMigration-primary";
         final String profilePassword = "testManagedProfileSeparateChallengeMigration-profile";
-        mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
-        mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, MANAGED_PROFILE_USER_ID);
+        mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+        mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
         final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
@@ -251,7 +277,8 @@
         mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+        mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
                 mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
@@ -271,8 +298,10 @@
         mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
-        mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+        mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, handle,
+                TOKEN.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+        mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
                 mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
@@ -293,9 +322,11 @@
         mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID);
+        mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD,
+                PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
 
-        mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+        mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                handle, TOKEN.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK,
                 mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
@@ -326,7 +357,7 @@
         // Set up pre-SP user password
         disableSyntheticPassword(PRIMARY_USER_ID);
         mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PRIMARY_USER_ID);
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
         enableSyntheticPassword(PRIMARY_USER_ID);
 
         long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
@@ -340,10 +371,51 @@
         assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
+    public void testPasswordData_serializeDeserialize() {
+        PasswordData data = new PasswordData();
+        data.scryptN = 11;
+        data.scryptR = 22;
+        data.scryptP = 33;
+        data.passwordType = CREDENTIAL_TYPE_PASSWORD;
+        data.salt = PAYLOAD;
+        data.passwordHandle = PAYLOAD2;
+
+        PasswordData deserialized = PasswordData.fromBytes(data.toBytes());
+
+        assertEquals(11, deserialized.scryptN);
+        assertEquals(22, deserialized.scryptR);
+        assertEquals(33, deserialized.scryptP);
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+        assertArrayEquals(PAYLOAD, deserialized.salt);
+        assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+    }
+
+    public void testPasswordData_deserialize() {
+        // Test that we can deserialize existing PasswordData and don't inadvertently change the
+        // wire format.
+        byte[] serialized = new byte[] {
+                0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD */
+                11, /* scryptN */
+                22, /* scryptR */
+                33, /* scryptP */
+                0, 0, 0, 5, /* salt.length */
+                1, 2, -1, -2, 55, /* salt */
+                0, 0, 0, 6, /* passwordHandle.length */
+                2, 3, -2, -3, 44, 1, /* passwordHandle */
+        };
+        PasswordData deserialized = PasswordData.fromBytes(serialized);
+
+        assertEquals(11, deserialized.scryptN);
+        assertEquals(22, deserialized.scryptR);
+        assertEquals(33, deserialized.scryptP);
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+        assertArrayEquals(PAYLOAD, deserialized.salt);
+        assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+    }
+
     // b/34600579
     //TODO: add non-migration work profile case, and unify/un-unify transition.
     //TODO: test token after user resets password
     //TODO: test token based reset after unified work challenge
     //TODO: test clear password after unified work challenge
 }
-