Merge "Create a verify flag to support verification for entering repair mode" into udc-qpr-dev
This commit is contained in:
@@ -116,6 +116,17 @@ public class KeyguardManager {
|
||||
public static final String ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL =
|
||||
"android.app.action.CONFIRM_REMOTE_DEVICE_CREDENTIAL";
|
||||
|
||||
/**
|
||||
* Intent used to prompt user for device credential for entering repair
|
||||
* mode. If the credential is verified successfully, then the information
|
||||
* needed to verify the credential again will be written to a location that
|
||||
* is available to repair mode. This makes it possible for repair mode to
|
||||
* require that the same credential be provided to exit repair mode.
|
||||
* @hide
|
||||
*/
|
||||
public static final String ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL =
|
||||
"android.app.action.PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL";
|
||||
|
||||
/**
|
||||
* A CharSequence dialog title to show to the user when used with a
|
||||
* {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
|
||||
|
||||
@@ -47,6 +47,7 @@ import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.storage.StorageManager;
|
||||
@@ -160,9 +161,17 @@ public class LockPatternUtils {
|
||||
*/
|
||||
public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
|
||||
|
||||
/**
|
||||
* Flag provided to {@link #verifyCredential(LockscreenCredential, int, int)} . If set, the
|
||||
* method writes the password data to the repair mode file after the credential is verified
|
||||
* successfully.
|
||||
*/
|
||||
public static final int VERIFY_FLAG_WRITE_REPAIR_MODE_PW = 1 << 1;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, value = {
|
||||
VERIFY_FLAG_REQUEST_GK_PW_HANDLE
|
||||
VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
|
||||
VERIFY_FLAG_WRITE_REPAIR_MODE_PW
|
||||
})
|
||||
public @interface VerifyFlag {}
|
||||
|
||||
@@ -201,6 +210,8 @@ public class LockPatternUtils {
|
||||
public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
|
||||
public static final String PASSWORD_HISTORY_DELIMITER = ",";
|
||||
|
||||
private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
|
||||
|
||||
/**
|
||||
* drives the pin auto confirmation feature availability in code logic.
|
||||
*/
|
||||
@@ -1846,6 +1857,37 @@ public class LockPatternUtils {
|
||||
com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if repair mode is supported by the device.
|
||||
*/
|
||||
public static boolean isRepairModeSupported(Context context) {
|
||||
return context.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_repairModeSupported);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if repair mode is active on the device.
|
||||
*/
|
||||
public static boolean isRepairModeActive(Context context) {
|
||||
return Settings.Global.getInt(context.getContentResolver(),
|
||||
Settings.Global.REPAIR_MODE_ACTIVE, /* def= */ 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if repair mode is supported by the device and the user has been granted
|
||||
* admin privileges.
|
||||
*/
|
||||
public static boolean canUserEnterRepairMode(Context context, UserInfo info) {
|
||||
return info != null && info.isAdmin() && isRepairModeSupported(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if GSI is running on the device.
|
||||
*/
|
||||
public static boolean isGsiRunning() {
|
||||
return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to rederive the unified work challenge for the specified profile user and unlock the
|
||||
* user. If successful, this would allow the user to leave quiet mode automatically without
|
||||
|
||||
@@ -41,6 +41,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
|
||||
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
|
||||
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
|
||||
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
|
||||
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
|
||||
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
|
||||
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
|
||||
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
|
||||
@@ -278,8 +279,6 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
protected IGateKeeperService mGateKeeperService;
|
||||
protected IAuthSecret mAuthSecretService;
|
||||
|
||||
private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
|
||||
|
||||
/**
|
||||
* The UIDs that are used for system credential storage in keystore.
|
||||
*/
|
||||
@@ -311,6 +310,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
|
||||
mLockSettingsService.migrateOldDataAfterSystemReady();
|
||||
mLockSettingsService.loadEscrowData();
|
||||
mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
|
||||
public boolean isGsiRunning() {
|
||||
return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
|
||||
return LockPatternUtils.isGsiRunning();
|
||||
}
|
||||
|
||||
public FingerprintManager getFingerprintManager() {
|
||||
@@ -949,6 +949,16 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
return success;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void deleteRepairModePersistentDataIfNeeded() {
|
||||
if (!LockPatternUtils.isRepairModeSupported(mContext)
|
||||
|| LockPatternUtils.isRepairModeActive(mContext)
|
||||
|| mInjector.isGsiRunning()) {
|
||||
return;
|
||||
}
|
||||
mStorage.deleteRepairModePersistentData();
|
||||
}
|
||||
|
||||
// This is called when Weaver is guaranteed to be available (if the device supports Weaver).
|
||||
// It does any synthetic password related work that was delayed from earlier in the boot.
|
||||
private void onThirdPartyAppsStarted() {
|
||||
@@ -2202,6 +2212,12 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
response = authResult.gkResponse;
|
||||
|
||||
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
|
||||
if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) {
|
||||
if (!mSpManager.writeRepairModeCredentialLocked(protectorId, userId)) {
|
||||
Slog.e(TAG, "Failed to write repair mode credential");
|
||||
return VerifyCredentialResponse.ERROR;
|
||||
}
|
||||
}
|
||||
// credential has matched
|
||||
mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
|
||||
authResult.syntheticPassword.deriveGkPassword());
|
||||
|
||||
@@ -90,6 +90,9 @@ class LockSettingsStorage {
|
||||
|
||||
private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
|
||||
|
||||
private static final String REPAIR_MODE_DIRECTORY = "repair-mode/";
|
||||
private static final String REPAIR_MODE_PERSISTENT_FILE = "pst";
|
||||
|
||||
private static final Object DEFAULT = new Object();
|
||||
|
||||
private static final String[] SETTINGS_TO_BACKUP = new String[] {
|
||||
@@ -390,6 +393,29 @@ class LockSettingsStorage {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
File getRepairModePersistentDataFile() {
|
||||
final File directory = new File(Environment.getMetadataDirectory(), REPAIR_MODE_DIRECTORY);
|
||||
return new File(directory, REPAIR_MODE_PERSISTENT_FILE);
|
||||
}
|
||||
|
||||
public PersistentData readRepairModePersistentData() {
|
||||
final byte[] data = readFile(getRepairModePersistentDataFile());
|
||||
if (data == null) {
|
||||
return PersistentData.NONE;
|
||||
}
|
||||
return PersistentData.fromBytes(data);
|
||||
}
|
||||
|
||||
public void writeRepairModePersistentData(int persistentType, int userId, byte[] payload) {
|
||||
writeFile(getRepairModePersistentDataFile(),
|
||||
PersistentData.toBytes(persistentType, userId, /* qualityForUi= */0, payload));
|
||||
}
|
||||
|
||||
public void deleteRepairModePersistentData() {
|
||||
deleteFile(getRepairModePersistentDataFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the synthetic password state file for the given user ID, protector ID, and state name.
|
||||
* If the file already exists, then it is atomically replaced.
|
||||
@@ -583,6 +609,17 @@ class LockSettingsStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a concrete data structure to represent the minimal information from
|
||||
* a user's LSKF-based SP protector that is needed to verify the user's LSKF,
|
||||
* in combination with the corresponding Gatekeeper enrollment or Weaver slot.
|
||||
* It can be stored in {@link com.android.server.PersistentDataBlockService} for
|
||||
* FRP to live across factory resets not initiated via the Settings UI.
|
||||
* Written to {@link #REPAIR_MODE_PERSISTENT_FILE} to support verification for
|
||||
* exiting repair mode, since the device runs with an empty data partition in
|
||||
* repair mode and the same credential be provided to exit repair mode is
|
||||
* required.
|
||||
*/
|
||||
public static class PersistentData {
|
||||
static final byte VERSION_1 = 1;
|
||||
static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
|
||||
@@ -685,6 +722,19 @@ class LockSettingsStorage {
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
// Dump repair mode file states
|
||||
final File repairModeFile = getRepairModePersistentDataFile();
|
||||
if (repairModeFile.exists()) {
|
||||
pw.println(TextUtils.formatSimple("Repair Mode [%s]:", repairModeFile.getParent()));
|
||||
pw.increaseIndent();
|
||||
pw.println(TextUtils.formatSimple("%6d %s %s", repairModeFile.length(),
|
||||
LockSettingsService.timestampToString(repairModeFile.lastModified()),
|
||||
repairModeFile.getName()));
|
||||
final PersistentData data = readRepairModePersistentData();
|
||||
pw.println(TextUtils.formatSimple("type: %d, user id: %d, payload size: %d",
|
||||
data.type, data.userId, data.payload != null ? data.payload.length : 0));
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
static class DatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
@@ -1114,6 +1114,57 @@ class SyntheticPasswordManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the user's synthetic password data to the repair mode file.
|
||||
*
|
||||
* @param protectorId current LSKF based protectorId
|
||||
* @param userId user id of the user
|
||||
*/
|
||||
public boolean writeRepairModeCredentialLocked(long protectorId, int userId) {
|
||||
if (!shouldWriteRepairModeCredential(userId)) {
|
||||
return false;
|
||||
}
|
||||
final byte[] data = loadState(PASSWORD_DATA_NAME, protectorId, userId);
|
||||
if (data == null) {
|
||||
Slogf.w(TAG, "Password data not found for user %d", userId);
|
||||
return false;
|
||||
}
|
||||
final PasswordData pwd = PasswordData.fromBytes(data);
|
||||
if (isNoneCredential(pwd)) {
|
||||
Slogf.w(TAG, "User %d has NONE credential", userId);
|
||||
return false;
|
||||
}
|
||||
Slogf.d(TAG, "Writing repair mode credential tied to user %d", userId);
|
||||
final int weaverSlot = loadWeaverSlot(protectorId, userId);
|
||||
if (weaverSlot != INVALID_WEAVER_SLOT) {
|
||||
// write weaver password
|
||||
mStorage.writeRepairModePersistentData(
|
||||
PersistentData.TYPE_SP_WEAVER, weaverSlot, pwd.toBytes());
|
||||
} else {
|
||||
// write gatekeeper password
|
||||
mStorage.writeRepairModePersistentData(
|
||||
PersistentData.TYPE_SP_GATEKEEPER, userId, pwd.toBytes());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean shouldWriteRepairModeCredential(int userId) {
|
||||
final UserInfo userInfo = mUserManager.getUserInfo(userId);
|
||||
if (!LockPatternUtils.canUserEnterRepairMode(mContext, userInfo)) {
|
||||
Slogf.w(TAG, "User %d can't enter repair mode", userId);
|
||||
return false;
|
||||
}
|
||||
if (LockPatternUtils.isRepairModeActive(mContext)) {
|
||||
Slog.w(TAG, "Can't write repair mode credential while repair mode is already active");
|
||||
return false;
|
||||
}
|
||||
if (LockPatternUtils.isGsiRunning()) {
|
||||
Slog.w(TAG, "Can't write repair mode credential while GSI is running");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
|
||||
@@ -254,6 +254,8 @@ public abstract class BaseLockSettingsServiceTests {
|
||||
.thenReturn(true);
|
||||
when(res.getBoolean(eq(com.android.internal.R.bool.config_strongAuthRequiredOnBoot)))
|
||||
.thenReturn(true);
|
||||
when(res.getBoolean(eq(com.android.internal.R.bool.config_repairModeSupported)))
|
||||
.thenReturn(true);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,11 @@ public class LockSettingsStorageTestable extends LockSettingsStorage {
|
||||
return remapToStorageDir(super.getSyntheticPasswordDirectoryForUser(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
File getRepairModePersistentDataFile() {
|
||||
return remapToStorageDir(super.getRepairModePersistentDataFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
|
||||
return mPersistentDataBlockManager;
|
||||
|
||||
@@ -457,6 +457,31 @@ public class LockSettingsStorageTests {
|
||||
assertEquals(2, PersistentData.TYPE_SP_WEAVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepairMode_emptyPersistentData() {
|
||||
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepairMode_writeGatekeeperPersistentData() {
|
||||
mStorage.writeRepairModePersistentData(
|
||||
PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID, PAYLOAD);
|
||||
|
||||
final PersistentData data = mStorage.readRepairModePersistentData();
|
||||
assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
|
||||
assertArrayEquals(PAYLOAD, data.payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepairMode_writeWeaverPersistentData() {
|
||||
mStorage.writeRepairModePersistentData(
|
||||
PersistentData.TYPE_SP_WEAVER, SOME_USER_ID, PAYLOAD);
|
||||
|
||||
final PersistentData data = mStorage.readRepairModePersistentData();
|
||||
assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
|
||||
assertArrayEquals(PAYLOAD, data.payload);
|
||||
}
|
||||
|
||||
private static void assertArrayEquals(byte[] expected, byte[] actual) {
|
||||
if (!Arrays.equals(expected, actual)) {
|
||||
fail("expected:<" + Arrays.toString(expected) +
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.locksettings;
|
||||
|
||||
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import android.app.PropertyInvalidatedCache;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
|
||||
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LockscreenRepairModeTest extends BaseLockSettingsServiceTests {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
PropertyInvalidatedCache.disableForTestMode();
|
||||
mService.initializeSyntheticPassword(PRIMARY_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyPin_writeRepairModePW() {
|
||||
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
|
||||
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
|
||||
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
|
||||
mService.verifyCredential(
|
||||
newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
|
||||
.getResponseCode());
|
||||
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
|
||||
getCredentialType(mStorage.readRepairModePersistentData()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyPattern_writeRepairModePW() {
|
||||
mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
|
||||
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
|
||||
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
|
||||
mService.verifyCredential(
|
||||
newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
|
||||
.getResponseCode());
|
||||
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
|
||||
getCredentialType(mStorage.readRepairModePersistentData()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyPassword_writeRepairModePW() {
|
||||
mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
|
||||
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
|
||||
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
|
||||
mService.verifyCredential(
|
||||
newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
|
||||
.getResponseCode());
|
||||
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
|
||||
getCredentialType(mStorage.readRepairModePersistentData()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyCredential_writeRepairModePW_repairModeActive() {
|
||||
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
|
||||
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
|
||||
|
||||
setRepairModeActive(true);
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
|
||||
mService.verifyCredential(
|
||||
newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
|
||||
.getResponseCode());
|
||||
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteRepairModePersistentData() {
|
||||
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
|
||||
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
|
||||
mService.verifyCredential(
|
||||
newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
|
||||
.getResponseCode());
|
||||
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
|
||||
getCredentialType(mStorage.readRepairModePersistentData()));
|
||||
|
||||
mService.deleteRepairModePersistentDataIfNeeded();
|
||||
assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
|
||||
}
|
||||
|
||||
private void setRepairModeActive(boolean active) {
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REPAIR_MODE_ACTIVE, active ? 1 : 0);
|
||||
}
|
||||
|
||||
private static int getCredentialType(PersistentData persistentData) {
|
||||
if (persistentData == null || persistentData.payload == null) {
|
||||
return LockPatternUtils.CREDENTIAL_TYPE_NONE;
|
||||
}
|
||||
return PasswordData.fromBytes(persistentData.payload).credentialType;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user