Introduce LockscreenCredential

* Wrap credential bytes and type into one single object.

* Update all external APIs dealing with lockscreen passoword
  to use the LockscreenCredential class. Remove existing variants
  that handles pin/password/pattern separately.

* Coerce password quality passed to LockSettingsService into one
  of UNSPECIFIED, PATTERN, NUMERIC or ALPHABETIC (explained below).

* Update all clients & tests to interface with LockscreenCredential.

Note: LockscreenCredential distinguishes between PIN and password
in its public interfaces, this is to pave the way for the next
patch of formally introducing a CREDENTIAL_TYPE_PIN type and
getting rid of the requestedQuality being passed along (whose
sole purpose nowadays is to distinguish between PIN and password)
For now LockscreenCredential still uses the quality value internally
to make that distinction. This does result in a change to what
quality values LockSettingsService receives as part of credential
change: after this CL LSS will only see the quality being
one of UNSPECIFIED, PATTERN, NUMERIC or ALPHABETIC, while it used to
receive other qualities (NUMERIC_COMPLEX, ALPHANUMERIC etc) if device
admin sets a password policy. This shouldn't make any behaviour changes
though, because the new range of values is still sufficient to
distinguish between PIN/Pattern/Password, which is what the consumers
of the stored quality care about.

Bug: 65239740
Test: atest com.android.server.locksettings
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest
Test: atest com.android.internal.widget.LockPatternUtilsTest
Test: atest com.android.internal.widget.LockscreenCredentialTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.password
Test: atest MixedManagedProfileOwnerTest#testResetPasswordWithToken
Test: atest MixedDeviceOwnerTest#testResetPasswordWithToken
Test: manually set an PIN/Pattern/Password; then change to
      PIN/Pattern/Password; finally remove password
Test: manually create a work profile; try unify and ununify work
      challenge.
Test: manually test lockscreen FRP flow (change password via Settings /
DPC)
Change-Id: I04cc04057c96292a7b1b672bff2a09d594ea9b3c
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index bad484f..63ba138 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -118,6 +118,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.CredentialType;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -2058,7 +2059,8 @@
             @UserIdInt int userHandle) {
         synchronized (this) {
             mUserPasswordMetrics.put(userHandle,
-                    PasswordMetrics.computeForCredential(credentialType, password));
+                    PasswordMetrics.computeForCredential(
+                            LockscreenCredential.createRaw(credentialType, password)));
         }
     }
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index a5d59e3..0a8e5bd 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -16,16 +16,12 @@
 
 package com.android.server.locksettings;
 
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
 import android.app.ActivityManager;
 import android.os.ShellCommand;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+import com.android.internal.widget.LockscreenCredential;
 
 import java.io.PrintWriter;
 
@@ -189,31 +185,49 @@
                 mLockPatternUtils.isSyntheticPasswordEnabled()));
     }
 
+    private LockscreenCredential getOldCredential() {
+        if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) {
+            final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId);
+            if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
+                return LockscreenCredential.createPassword(mOld);
+            } else {
+                return LockscreenCredential.createPin(mOld);
+            }
+        } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
+            return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+                    mOld.getBytes()));
+        } else {
+            return LockscreenCredential.createNone();
+        }
+    }
+
     private void runSetPattern() {
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.saveLockPattern(stringToPattern(mNew), oldBytes, mCurrentUserId);
+        mLockPatternUtils.setLockCredential(
+                LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+                        mNew.getBytes())),
+                getOldCredential(),
+                mCurrentUserId);
         getOutPrintWriter().println("Pattern set to '" + mNew + "'");
     }
 
     private void runSetPassword() {
-        byte[] newBytes = mNew != null ? mNew.getBytes() : null;
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_ALPHABETIC,
+        mLockPatternUtils.setLockCredential(LockscreenCredential.createPassword(mNew),
+                getOldCredential(),
                 mCurrentUserId);
         getOutPrintWriter().println("Password set to '" + mNew + "'");
     }
 
     private void runSetPin() {
-        byte[] newBytes = mNew != null ? mNew.getBytes() : null;
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_NUMERIC,
+        mLockPatternUtils.setLockCredential(LockscreenCredential.createPin(mNew),
+                getOldCredential(),
                 mCurrentUserId);
         getOutPrintWriter().println("Pin set to '" + mNew + "'");
     }
 
     private void runClear() {
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.clearLock(oldBytes, mCurrentUserId);
+        mLockPatternUtils.setLockCredential(LockscreenCredential.createNone(),
+                getOldCredential(),
+                mCurrentUserId);
         getOutPrintWriter().println("Lock credential cleared");
     }
 
@@ -238,13 +252,8 @@
             }
 
             try {
-                final boolean result;
-                if (havePassword) {
-                    byte[] passwordBytes = mOld != null ? mOld.getBytes() : null;
-                    result = mLockPatternUtils.checkPassword(passwordBytes, mCurrentUserId);
-                } else {
-                    result = mLockPatternUtils.checkPattern(stringToPattern(mOld), mCurrentUserId);
-                }
+                final boolean result = mLockPatternUtils.checkCredential(getOldCredential(),
+                        mCurrentUserId, null);
                 if (!result) {
                     if (!mLockPatternUtils.isManagedProfileWithUnifiedChallenge(mCurrentUserId)) {
                         mLockPatternUtils.reportFailedPasswordAttempt(mCurrentUserId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b118cdf..5f44918 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -251,6 +251,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemServerInitThreadPool;
@@ -5222,28 +5223,20 @@
         // back in to the service.
         final long ident = mInjector.binderClearCallingIdentity();
         final boolean result;
+        final LockscreenCredential newCredential =
+                LockscreenCredential.createPasswordOrNone(password);
         try {
             if (token == null) {
                 // This is the legacy reset password for DPM. Here we want to be able to override
                 // the old device password without necessarily knowing it.
-                if (!TextUtils.isEmpty(password)) {
-                    mLockPatternUtils.saveLockPassword(password.getBytes(), null, quality,
-                            userHandle, /*allowUntrustedChange */true);
-                } else {
-                    mLockPatternUtils.clearLock(null, userHandle,
-                            /*allowUntrustedChange */ true);
-                }
+                mLockPatternUtils.setLockCredential(
+                        newCredential,
+                        LockscreenCredential.createNone(),
+                        userHandle, /*allowUntrustedChange */true);
                 result = true;
             } else {
-                if (!TextUtils.isEmpty(password)) {
-                    result = mLockPatternUtils.setLockCredentialWithToken(password.getBytes(),
-                            LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                            quality, tokenHandle, token, userHandle);
-                } else {
-                    result = mLockPatternUtils.setLockCredentialWithToken(null,
-                            LockPatternUtils.CREDENTIAL_TYPE_NONE,
-                            quality, tokenHandle, token, userHandle);
-                }
+                result = mLockPatternUtils.setLockCredentialWithToken(newCredential, tokenHandle,
+                        token, userHandle);
             }
             boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
             if (requireEntry) {
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 a25e40f..1cec3df 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -93,7 +93,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
@@ -4251,9 +4251,9 @@
         assertTrue(dpm.isResetPasswordTokenActive(admin1));
 
         // test reset password with token
-        when(getServices().lockPatternUtils.setLockCredentialWithToken(eq(password.getBytes()),
-                eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD),
-                eq(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), eq(handle), eq(token),
+        when(getServices().lockPatternUtils.setLockCredentialWithToken(
+                eq(LockscreenCredential.createPassword(password)),
+                eq(handle), eq(token),
                 eq(UserHandle.USER_SYSTEM)))
                 .thenReturn(true);
         assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index c00d33b..b60111e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -19,10 +19,9 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
 
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
 import static junit.framework.Assert.assertEquals;
 
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
@@ -48,6 +47,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -55,6 +56,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 /**
  * Test class for {@link LockSettingsShellCommand}.
  *
@@ -87,24 +90,30 @@
     public void testWrongPassword() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
-        when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(false);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(false);
         assertEquals(-1, mCommand.exec(mBinder, in, out, err,
                 new String[] { "set-pin", "--old", "1234" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils, never()).saveLockPassword(any(byte[].class), any(byte[].class),
-                anyInt(), anyInt());
+        verify(mLockPatternUtils, never()).setLockCredential(any(), any(),
+                anyInt(), anyBoolean());
     }
 
     @Test
     public void testChangePin() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
-        when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+                PASSWORD_QUALITY_NUMERIC);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-pin", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
-                PASSWORD_QUALITY_NUMERIC, mUserId);
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createPin("4321"),
+                LockscreenCredential.createPin("1234"),
+                mUserId);
     }
 
     @Test
@@ -121,12 +130,17 @@
     public void testChangePassword() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
-        when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+                PASSWORD_QUALITY_ALPHABETIC);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
         assertEquals(0,  mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-password", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
-                PASSWORD_QUALITY_ALPHABETIC, mUserId);
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createPassword("4321"),
+                LockscreenCredential.createPassword("1234"),
+                mUserId);
     }
 
     @Test
@@ -143,11 +157,15 @@
     public void testChangePattern() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
-        when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId, null)).thenReturn(true);
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-pattern", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234".getBytes(),
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createPattern(stringToPattern("4321")),
+                LockscreenCredential.createPattern(stringToPattern("1234")),
                 mUserId);
     }
 
@@ -165,10 +183,19 @@
     public void testClear() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
-        when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId, null)).thenReturn(true);
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "clear", "--old", "1234" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).clearLock("1234".getBytes(), mUserId);
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createNone(),
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId);
+    }
+
+    private List<LockPatternView.Cell> stringToPattern(String str) {
+        return LockPatternUtils.byteArrayToPattern(str.getBytes());
     }
 }
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 0776589..85c73c6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
@@ -364,7 +365,7 @@
         // Verify DPM gets notified about new device lock
         flushHandlerTasks();
         final PasswordMetrics metric = PasswordMetrics.computeForCredential(
-                LockPatternUtils.CREDENTIAL_TYPE_PATTERN, pattern);
+                LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(pattern)));
         assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
         verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);