Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e891bcc authored by Rubin Xu's avatar Rubin Xu
Browse files

Introduce Lockscreen PIN credential as first-class citizen

* Add CREDENTIAL_TYPE_PIN as the fourth credential type.
* Rename existing CREDENTIAL_TYPE_PASSWORD
  to CREDENTIAL_TYPE_PASSWORD_OR_PIN which is still referenced
  by password data persisted on disk.
* No longer store quality for new credentials (PASSWORD_TYPE_KEY).
  Credential type stored in synthetic password blob is now the single
  source of truth on what credential (None/Pin/Pattern/Password) the
  device currently has.
* Adapt lockscreen FRP to work on a similar fashion (no more quality
  being passed around and stored)
* Adapt RecoverableKeystore to use the new PIN credential type.
* Fix existing unit tests
* Add new unit tests for lockscreen FRP.

Upgrade path:
* Existing credentials will have CREDENTIAL_TYPE_PASSWORD_OR_PIN, and when
  LSS sees this, it will further consult PASSWORD_TYPE_KEY to distinguish
  between PIN and Pattern. The credential will stay this way until the next
  password change i.e. no automatic credential upgrade.
* Existing FRP credential will have CREDENTIAL_TYPE_PASSWORD_OR_PIN, and
  when LSS sees this, it will further consult the saved quality
  PersistentData.qualityForUi to make that distinction.
* Normal and FRP credential enrolled after this CL will store
  CREDENTIAL_TYPE_PIN to indicate this is a numeric PIN.

Bug: 65239740
Test: atest com.android.server.locksettings
Test: atest com.android.internal.widget.LockscreenCredentialTest
Test: atest com.android.internal.util.LockPatternUtilsTest
Test: atest LockSettingsShellCommandTest
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest
Test: atest FrameworksCoreTests:PasswordMetricsTest
Test: atest FrameworksCoreTests:PasswordPolicyTest
Test: atest MixedManagedProfileOwnerTest#testResetPasswordWithToken
Test: atest com.android.cts.devicepolicy.PasswordComplexityTest
Test: atest com.android.cts.devicepolicy.ManagedProfilePasswordTest
Test: flash an old build, enroll password and flash to new build.
      Verify everything still works.
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: I781cea4c32d567aac4af692697c4569161580102
parent bb883208
Loading
Loading
Loading
Loading
+26 −19
Original line number Diff line number Diff line
@@ -73,6 +73,11 @@ public final class PasswordMetrics implements Parcelable {
    // consider it a complex PIN/password.
    public static final int MAX_ALLOWED_SEQUENCE = 3;

    // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN or CREDENTIAL_TYPE_PASSWORD.
    // Note that this class still uses CREDENTIAL_TYPE_PASSWORD to represent both numeric PIN
    // and alphabetic password. This is OK as long as this definition is only used internally,
    // and the value never gets mixed up with credential types from other parts of the framework.
    // TODO: fix this (ideally after we move logic to PasswordPolicy)
    public @CredentialType int credType;
    // Fields below only make sense when credType is PASSWORD.
    public int length = 0;
@@ -161,6 +166,7 @@ public final class PasswordMetrics implements Parcelable {

    public static final @NonNull Parcelable.Creator<PasswordMetrics> CREATOR
            = new Parcelable.Creator<PasswordMetrics>() {
                @Override
                public PasswordMetrics createFromParcel(Parcel in) {
                    int credType = in.readInt();
                    int length = in.readInt();
@@ -172,10 +178,11 @@ public final class PasswordMetrics implements Parcelable {
                    int nonLetter = in.readInt();
                    int nonNumeric = in.readInt();
                    int seqLength = in.readInt();
            return new PasswordMetrics(credType, length, letters, upperCase, lowerCase, numeric,
                    symbols, nonLetter, nonNumeric, seqLength);
                    return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
                            numeric, symbols, nonLetter, nonNumeric, seqLength);
                }

                @Override
                public PasswordMetrics[] newArray(int size) {
                    return new PasswordMetrics[size];
                }
@@ -189,7 +196,7 @@ public final class PasswordMetrics implements Parcelable {
     * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
     */
    public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
        if (credential.isPassword()) {
        if (credential.isPassword() || credential.isPin()) {
            return PasswordMetrics.computeForPassword(credential.getCredential());
        } else if (credential.isPattern())  {
            return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+1 −4
Original line number Diff line number Diff line
@@ -50,10 +50,7 @@ interface ILockSettings {
    VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, long challenge, int userId);
    VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, long challenge, int userId);
    boolean checkVoldPassword(int userId);
    @UnsupportedAppUsage
    boolean havePattern(int userId);
    @UnsupportedAppUsage
    boolean havePassword(int userId);
    int getCredentialType(int userId);
    byte[] getHashFactor(in LockscreenCredential currentCredential, int userId);
    void setSeparateProfileChallengeEnabled(int userId, boolean enabled, in LockscreenCredential managedUserPassword);
    boolean getSeparateProfileChallengeEnabled(int userId);
+60 −85
Original line number Diff line number Diff line
@@ -17,9 +17,6 @@
package com.android.internal.widget;

import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -117,13 +114,19 @@ public class LockPatternUtils {
    // NOTE: When modifying this, make sure credential sufficiency validation logic is intact.
    public static final int CREDENTIAL_TYPE_NONE = -1;
    public static final int CREDENTIAL_TYPE_PATTERN = 1;
    public static final int CREDENTIAL_TYPE_PASSWORD = 2;
    // This is the legacy value persisted on disk. Never return it to clients, but internally
    // we still need it to handle upgrade cases.
    public static final int CREDENTIAL_TYPE_PASSWORD_OR_PIN = 2;
    public static final int CREDENTIAL_TYPE_PIN = 3;
    public static final int CREDENTIAL_TYPE_PASSWORD = 4;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = {"CREDENTIAL_TYPE_"}, value = {
            CREDENTIAL_TYPE_NONE,
            CREDENTIAL_TYPE_PATTERN,
            CREDENTIAL_TYPE_PASSWORD, // Either pin or password.
            CREDENTIAL_TYPE_PASSWORD,
            CREDENTIAL_TYPE_PIN,
            // CREDENTIAL_TYPE_PASSWORD_OR_PIN is missing on purpose.
    })
    public @interface CredentialType {}

@@ -169,6 +172,7 @@ public class LockPatternUtils {

    public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
    public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
    public static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
    private static final String HISTORY_DELIMITER = ",";

    @UnsupportedAppUsage
@@ -372,8 +376,8 @@ public class LockPatternUtils {
     *
     * @param credential The credential to check.
     * @param challenge The challenge to verify against the credential
     * @return the attestation that the challenge was verified, or null
     * @param userId The user whose credential is being verified
     * @return the attestation that the challenge was verified, or null
     * @throws RequestThrottledException if credential verification is being throttled due to
     *         to many incorrect attempts.
     * @throws IllegalStateException if called on the main thread.
@@ -392,6 +396,7 @@ public class LockPatternUtils {
                return null;
            }
        } catch (RemoteException re) {
            Log.e(TAG, "failed to verify credential", re);
            return null;
        }
    }
@@ -423,6 +428,7 @@ public class LockPatternUtils {
                return false;
            }
        } catch (RemoteException re) {
            Log.e(TAG, "failed to check credential", re);
            return false;
        }
    }
@@ -456,6 +462,7 @@ public class LockPatternUtils {
                return null;
            }
        } catch (RemoteException re) {
            Log.e(TAG, "failed to verify tied profile credential", re);
            return null;
        }
    }
@@ -469,6 +476,7 @@ public class LockPatternUtils {
        try {
            return getLockSettings().checkVoldPassword(userId);
        } catch (RemoteException re) {
            Log.e(TAG, "failed to check vold password", re);
            return false;
        }
    }
@@ -521,30 +529,6 @@ public class LockPatternUtils {
        return false;
    }

    /**
     * Check to see if the user has stored a lock pattern.
     * @return Whether a saved pattern exists.
     */
    private boolean savedPatternExists(int userId) {
        try {
            return getLockSettings().havePattern(userId);
        } catch (RemoteException re) {
            return false;
        }
    }

    /**
     * Check to see if the user has stored a lock pattern.
     * @return Whether a saved pattern exists.
     */
    private boolean savedPasswordExists(int userId) {
        try {
            return getLockSettings().havePassword(userId);
        } catch (RemoteException re) {
            return false;
        }
    }

    /**
     * Return true if the user has ever chosen a pattern.  This is true even if the pattern is
     * currently cleared.
@@ -566,22 +550,11 @@ public class LockPatternUtils {
    /**
     * Used by device policy manager to validate the current password
     * information it has.
     * @Deprecated use {@link #getKeyguardStoredPasswordQuality}
     */
    @UnsupportedAppUsage
    public int getActivePasswordQuality(int userId) {
        int quality = getKeyguardStoredPasswordQuality(userId);

        if (isLockPasswordEnabled(quality, userId)) {
            // Quality is a password and a password exists. Return the quality.
            return quality;
        }

        if (isLockPatternEnabled(quality, userId)) {
            // Quality is a pattern and a pattern exists. Return the quality.
            return quality;
        }

        return PASSWORD_QUALITY_UNSPECIFIED;
        return getKeyguardStoredPasswordQuality(userId);
    }

    /**
@@ -639,6 +612,22 @@ public class LockPatternUtils {
        return quality == PASSWORD_QUALITY_NUMERIC || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
    }

    /** Returns the canonical password quality corresponding to the given credential type. */
    public static int credentialTypeToPasswordQuality(int credentialType) {
        switch (credentialType) {
            case CREDENTIAL_TYPE_NONE:
                return PASSWORD_QUALITY_UNSPECIFIED;
            case CREDENTIAL_TYPE_PATTERN:
                return PASSWORD_QUALITY_SOMETHING;
            case CREDENTIAL_TYPE_PIN:
                return PASSWORD_QUALITY_NUMERIC;
            case CREDENTIAL_TYPE_PASSWORD:
                return PASSWORD_QUALITY_ALPHABETIC;
            default:
                throw new IllegalStateException("Unknown type: " + credentialType);
        }
    }

    /**
     * Save a new lockscreen credential.
     *
@@ -682,17 +671,12 @@ public class LockPatternUtils {
        }
        newCredential.checkLength();

        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
        setKeyguardStoredPasswordQuality(newCredential.getQuality(), userHandle);

        try {
            if (!getLockSettings().setLockCredential(
                    newCredential, savedCredential, userHandle, allowUntrustedChange)) {
                setKeyguardStoredPasswordQuality(currentQuality, userHandle);
                return false;
            }
        } catch (RemoteException | RuntimeException e) {
            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
        } catch (RemoteException e) {
            throw new RuntimeException("Unable to save lock password", e);
        }

@@ -900,14 +884,12 @@ public class LockPatternUtils {
     * @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)
     *
     * @return stored password quality
     * @deprecated use {@link #getCredentialTypeForUser(int)} instead
     */
    @UnsupportedAppUsage
    @Deprecated
    public int getKeyguardStoredPasswordQuality(int userHandle) {
        return (int) getLong(PASSWORD_TYPE_KEY, PASSWORD_QUALITY_UNSPECIFIED, userHandle);
    }

    private void setKeyguardStoredPasswordQuality(int quality, int userHandle) {
        setLong(PASSWORD_TYPE_KEY, quality, userHandle);
        return credentialTypeToPasswordQuality(getCredentialTypeForUser(userHandle));
    }

    /**
@@ -1093,29 +1075,34 @@ public class LockPatternUtils {
        }
    }

    /**
     * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
     * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
     * {@link #CREDENTIAL_TYPE_PASSWORD}
     */
    public @CredentialType int getCredentialTypeForUser(int userHandle) {
        try {
            return getLockSettings().getCredentialType(userHandle);
        } catch (RemoteException re) {
            Log.e(TAG, "failed to get credential type", re);
            return CREDENTIAL_TYPE_NONE;
        }
    }

    /**
     * @param userId the user for which to report the value
     * @return Whether the lock screen is secured.
     */
    @UnsupportedAppUsage
    public boolean isSecure(int userId) {
        int mode = getKeyguardStoredPasswordQuality(userId);
        return isLockPatternEnabled(mode, userId) || isLockPasswordEnabled(mode, userId);
        int type = getCredentialTypeForUser(userId);
        return type != CREDENTIAL_TYPE_NONE;
    }

    @UnsupportedAppUsage
    public boolean isLockPasswordEnabled(int userId) {
        return isLockPasswordEnabled(getKeyguardStoredPasswordQuality(userId), userId);
    }

    private boolean isLockPasswordEnabled(int mode, int userId) {
        final boolean passwordEnabled = mode == PASSWORD_QUALITY_ALPHABETIC
                || mode == PASSWORD_QUALITY_NUMERIC
                || mode == PASSWORD_QUALITY_NUMERIC_COMPLEX
                || mode == PASSWORD_QUALITY_ALPHANUMERIC
                || mode == PASSWORD_QUALITY_COMPLEX
                || mode == PASSWORD_QUALITY_MANAGED;
        return passwordEnabled && savedPasswordExists(userId);
        int type = getCredentialTypeForUser(userId);
        return type == CREDENTIAL_TYPE_PASSWORD || type == CREDENTIAL_TYPE_PIN;
    }

    /**
@@ -1123,7 +1110,8 @@ public class LockPatternUtils {
     */
    @UnsupportedAppUsage
    public boolean isLockPatternEnabled(int userId) {
        return isLockPatternEnabled(getKeyguardStoredPasswordQuality(userId), userId);
        int type = getCredentialTypeForUser(userId);
        return type == CREDENTIAL_TYPE_PATTERN;
    }

    @Deprecated
@@ -1139,10 +1127,6 @@ public class LockPatternUtils {
        setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, true, userId);
    }

    private boolean isLockPatternEnabled(int mode, int userId) {
        return mode == PASSWORD_QUALITY_SOMETHING && savedPatternExists(userId);
    }

    /**
     * @return Whether the visible pattern is enabled.
     */
@@ -1548,19 +1532,9 @@ public class LockPatternUtils {
        credential.checkLength();
        LockSettingsInternal localService = getLockSettingsInternal();

        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
        setKeyguardStoredPasswordQuality(credential.getQuality(), userHandle);

        try {
            if (!localService.setLockCredentialWithToken(
                    credential, tokenHandle, token, userHandle)) {
                setKeyguardStoredPasswordQuality(currentQuality, userHandle);
        if (!localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle)) {
            return false;
        }
        } catch (RuntimeException e) {
            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
            throw new RuntimeException("Unable to save lock credential", e);
        }

        onPostPasswordChanged(credential, userHandle);
        return true;
@@ -1760,7 +1734,8 @@ public class LockPatternUtils {
    }

    public boolean isSyntheticPasswordEnabled() {
        return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
        return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT,
                UserHandle.USER_SYSTEM) != 0;
    }

    /**
+40 −40
Original line number Diff line number Diff line
@@ -16,14 +16,11 @@

package com.android.internal.widget;

import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
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_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -61,9 +58,6 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
    // Stores raw credential bytes, or null if credential has been zeroized. An empty password
    // is represented as a byte array of length 0.
    private byte[] mCredential;
    // Store the quality of the password, this is used to distinguish between pin
    // (PASSWORD_QUALITY_NUMERIC) and password (PASSWORD_QUALITY_ALPHABETIC).
    private final int mQuality;

    /**
     * Private constructor, use static builder methods instead.
@@ -72,15 +66,18 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
     * LockscreenCredential will only store the reference internally without copying. This is to
     * minimize the number of extra copies introduced.
     */
    private LockscreenCredential(int type, int quality, byte[] credential) {
    private LockscreenCredential(int type, byte[] credential) {
        Preconditions.checkNotNull(credential);
        if (type == CREDENTIAL_TYPE_NONE) {
            Preconditions.checkArgument(credential.length == 0);
        } else {
            // Do not allow constructing a CREDENTIAL_TYPE_PASSWORD_OR_PIN object.
            Preconditions.checkArgument(type == CREDENTIAL_TYPE_PIN
                    || type == CREDENTIAL_TYPE_PASSWORD
                    || type == CREDENTIAL_TYPE_PATTERN);
            Preconditions.checkArgument(credential.length > 0);
        }
        mType = type;
        mQuality = quality;
        mCredential = credential;
    }

@@ -88,8 +85,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
     * Creates a LockscreenCredential object representing empty password.
     */
    public static LockscreenCredential createNone() {
        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, PASSWORD_QUALITY_UNSPECIFIED,
                new byte[0]);
        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, new byte[0]);
    }

    /**
@@ -97,7 +93,6 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
     */
    public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
                PASSWORD_QUALITY_SOMETHING,
                LockPatternUtils.patternToByteArray(pattern));
    }

@@ -106,7 +101,6 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
     */
    public static LockscreenCredential createPassword(@NonNull CharSequence password) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
                PASSWORD_QUALITY_ALPHABETIC,
                charSequenceToByteArray(password));
    }

@@ -118,7 +112,6 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
     */
    public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
                PASSWORD_QUALITY_ALPHABETIC,
                Arrays.copyOf(password, password.length));
    }

@@ -126,8 +119,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
     * Creates a LockscreenCredential object representing the given numeric PIN.
     */
    public static LockscreenCredential createPin(@NonNull CharSequence pin) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
                PASSWORD_QUALITY_NUMERIC,
        return new LockscreenCredential(CREDENTIAL_TYPE_PIN,
                charSequenceToByteArray(pin));
    }

@@ -160,24 +152,14 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
    }
    /**
     * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
     * {@link #CREDENTIAL_TYPE_PATTERN} or {@link #CREDENTIAL_TYPE_PASSWORD}.
     *
     * TODO: Remove once credential type is internal. Callers should use {@link #isNone},
     * {@link #isPattern} and {@link #isPassword} instead.
     * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} or
     * {@link #CREDENTIAL_TYPE_PASSWORD}.
     */
    public int getType() {
        ensureNotZeroized();
        return mType;
    }

    /**
     * Returns the quality type of the credential
     */
    public int getQuality() {
        ensureNotZeroized();
        return mQuality;
    }

    /**
     * Returns the credential bytes. This is a direct reference of the internal field so
     * callers should not modify it.
@@ -200,9 +182,11 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
        if (isPattern()) {
            return StorageManager.CRYPT_TYPE_PATTERN;
        }
        if (isPin()) {
            return StorageManager.CRYPT_TYPE_PIN;
        }
        if (isPassword()) {
            return mQuality == PASSWORD_QUALITY_NUMERIC
                    ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
            return StorageManager.CRYPT_TYPE_PASSWORD;
        }
        throw new IllegalStateException("Unhandled credential type");
    }
@@ -219,7 +203,13 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
        return mType == CREDENTIAL_TYPE_PATTERN;
    }

    /** Returns whether this is a password credential */
    /** Returns whether this is a numeric pin credential */
    public boolean isPin() {
        ensureNotZeroized();
        return mType == CREDENTIAL_TYPE_PIN;
    }

    /** Returns whether this is an alphabetic password credential */
    public boolean isPassword() {
        ensureNotZeroized();
        return mType == CREDENTIAL_TYPE_PASSWORD;
@@ -233,7 +223,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {

    /** Create a copy of the credential */
    public LockscreenCredential duplicate() {
        return new LockscreenCredential(mType, mQuality,
        return new LockscreenCredential(mType,
                mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
    }

@@ -263,7 +253,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
            }
            return;
        }
        if (isPassword()) {
        if (isPassword() || isPin()) {
            if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
                throw new IllegalArgumentException("password must not be null and at least "
                        + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
@@ -272,10 +262,22 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
        }
    }

    /**
     * Check if this credential's type matches one that's retrieved from disk. The nuance here is
     * that the framework used to not distinguish between PIN and password, so this method will
     * allow a PIN/Password LockscreenCredential to match against the legacy
     * {@link #CREDENTIAL_TYPE_PASSWORD_OR_PIN} stored on disk.
     */
    public boolean checkAgainstStoredType(int storedCredentialType) {
        if (storedCredentialType == CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
            return getType() == CREDENTIAL_TYPE_PASSWORD || getType() == CREDENTIAL_TYPE_PIN;
        }
        return getType() == storedCredentialType;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mType);
        dest.writeInt(mQuality);
        dest.writeByteArray(mCredential);
    }

@@ -284,8 +286,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {

        @Override
        public LockscreenCredential createFromParcel(Parcel source) {
            return new LockscreenCredential(source.readInt(), source.readInt(),
                    source.createByteArray());
            return new LockscreenCredential(source.readInt(), source.createByteArray());
        }

        @Override
@@ -307,7 +308,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
    @Override
    public int hashCode() {
        // Effective Java — Item 9
        return ((17 + mType) * 31 + mQuality) * 31 + mCredential.hashCode();
        return (17 + mType) * 31 + mCredential.hashCode();
    }

    @Override
@@ -315,8 +316,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
        if (o == this) return true;
        if (!(o instanceof LockscreenCredential)) return false;
        final LockscreenCredential other = (LockscreenCredential) o;
        return mType == other.mType && mQuality == other.mQuality
                && Arrays.equals(mCredential, other.mCredential);
        return mType == other.mType && Arrays.equals(mCredential, other.mCredential);
    }

    /**
+28 −1
Original line number Diff line number Diff line
@@ -31,10 +31,23 @@ public class LockscreenCredentialTest extends AndroidTestCase {
        assertEquals(0, empty.size());
        assertNotNull(empty.getCredential());

        assertFalse(empty.isPin());
        assertFalse(empty.isPassword());
        assertFalse(empty.isPattern());
    }

    public void testPinCredential() {
        LockscreenCredential pin = LockscreenCredential.createPin("3456");

        assertTrue(pin.isPin());
        assertEquals(4, pin.size());
        assertTrue(Arrays.equals("3456".getBytes(), pin.getCredential()));

        assertFalse(pin.isNone());
        assertFalse(pin.isPassword());
        assertFalse(pin.isPattern());
    }

    public void testPasswordCredential() {
        LockscreenCredential password = LockscreenCredential.createPassword("password");

@@ -43,6 +56,7 @@ public class LockscreenCredentialTest extends AndroidTestCase {
        assertTrue(Arrays.equals("password".getBytes(), password.getCredential()));

        assertFalse(password.isNone());
        assertFalse(password.isPin());
        assertFalse(password.isPattern());
    }

@@ -60,6 +74,7 @@ public class LockscreenCredentialTest extends AndroidTestCase {
        assertTrue(Arrays.equals("12369".getBytes(), pattern.getCredential()));

        assertFalse(pattern.isNone());
        assertFalse(pattern.isPin());
        assertFalse(pattern.isPassword());
    }

@@ -72,6 +87,15 @@ public class LockscreenCredentialTest extends AndroidTestCase {
                LockscreenCredential.createPasswordOrNone("abcd"));
    }

    public void testPinOrNoneCredential() {
        assertEquals(LockscreenCredential.createNone(),
                LockscreenCredential.createPinOrNone(null));
        assertEquals(LockscreenCredential.createNone(),
                LockscreenCredential.createPinOrNone(""));
        assertEquals(LockscreenCredential.createPin("1357"),
                LockscreenCredential.createPinOrNone("1357"));
    }

    public void testSanitize() {
        LockscreenCredential password = LockscreenCredential.createPassword("password");
        password.zeroize();
@@ -80,11 +104,14 @@ public class LockscreenCredentialTest extends AndroidTestCase {
            password.isNone();
            fail("Sanitized credential still accessible");
        } catch (IllegalStateException expected) { }

        try {
            password.isPattern();
            fail("Sanitized credential still accessible");
        } catch (IllegalStateException expected) { }
        try {
            password.isPin();
            fail("Sanitized credential still accessible");
        } catch (IllegalStateException expected) { }
        try {
            password.isPassword();
            fail("Sanitized credential still accessible");
Loading