Always trigger face auth even when it is locked out.
1. This is the only way to show a message on keyguard that face unlock is not available in response to all face unlock triggers.
2. This was the behavior of face auth until recently (b/244402839#comment11)
Other options:
- Add a new callback method to a KeyguardUpateMonitorCallback to indicate that face auth trigger occurred.
- Adding a new method to a long list of methods in KeyguardUpateMonitorCallback is not a good idea
- This would be a very specific API that no one else might even need.
Indication message shown on keyguard:
1. first time face lockout.
- Shows "Too many attempts. Face Unlock disabled." followed by "Use fingerprint instead" or "Swipe up to open"
2. while the face is already locked out
- when the device wakes up
- "Face Unlock unavailable." followed by "Use fingerprint instead" or "Swipe up to open"
3. Coex when both face and fingerprint are locked out
- when the device wakes up
- Existing behavior is retained, nothing is shown.
No difference in behavior between bypass vs non-bypass
Bug: 247688462
Test: atest KeyguardUpdateMonitorTest
Change-Id: I3c7e15818eedfef9b5af9dd0b0fc29b7fda231e1
This commit is contained in:
@@ -403,6 +403,8 @@
|
||||
<string name="keyguard_face_failed">Can\u2019t recognize face</string>
|
||||
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
|
||||
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
|
||||
<!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] -->
|
||||
<string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
|
||||
|
||||
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
|
||||
|
||||
@@ -798,7 +798,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
||||
mFingerprintCancelSignal = null;
|
||||
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
|
||||
FACE_AUTH_UPDATED_FP_AUTHENTICATED);
|
||||
mLogger.d("onFingerprintAuthenticated");
|
||||
mLogger.logFingerprintSuccess(userId, isStrongBiometric);
|
||||
for (int i = 0; i < mCallbacks.size(); i++) {
|
||||
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
|
||||
if (cb != null) {
|
||||
@@ -2716,7 +2716,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
||||
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
|
||||
|
||||
// TODO: always disallow when fp is already locked out?
|
||||
final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
|
||||
final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
|
||||
|
||||
final boolean canBypass = mKeyguardBypassController != null
|
||||
&& mKeyguardBypassController.canBypass();
|
||||
@@ -2745,7 +2745,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
||||
final boolean faceDisabledForUser = isFaceDisabled(user);
|
||||
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
|
||||
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
|
||||
final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
|
||||
|
||||
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
|
||||
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
|
||||
@@ -2763,7 +2762,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
||||
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
|
||||
&& !faceAuthenticated
|
||||
&& !mGoingToSleep
|
||||
&& !fpOrFaceIsLockedOut;
|
||||
// We only care about fp locked out state and not face because we still trigger
|
||||
// face auth even when face is locked out to show the user a message that face
|
||||
// unlock was supposed to run but didn't
|
||||
&& !fpLockedOut;
|
||||
|
||||
// Aggregate relevant fields for debug logging.
|
||||
maybeLogListenerModelData(
|
||||
@@ -2778,7 +2780,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
||||
faceAuthenticated,
|
||||
faceDisabledForUser,
|
||||
isFaceLockedOut(),
|
||||
fpLockedout,
|
||||
fpLockedOut,
|
||||
mGoingToSleep,
|
||||
awakeKeyguard,
|
||||
mKeyguardGoingAway,
|
||||
|
||||
@@ -153,6 +153,13 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
|
||||
{ "fingerprintRunningState: $int1" })
|
||||
}
|
||||
|
||||
fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
|
||||
logBuffer.log(TAG, DEBUG, {
|
||||
int1 = userId
|
||||
bool1 = isStrongBiometric
|
||||
}, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
|
||||
}
|
||||
|
||||
fun logInvalidSubId(subId: Int) {
|
||||
logBuffer.log(TAG, INFO,
|
||||
{ int1 = subId },
|
||||
|
||||
@@ -79,6 +79,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.settingslib.fuelgauge.BatteryStatus;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.biometrics.AuthController;
|
||||
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
@@ -137,6 +138,7 @@ public class KeyguardIndicationController {
|
||||
private final KeyguardStateController mKeyguardStateController;
|
||||
protected final StatusBarStateController mStatusBarStateController;
|
||||
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||
private final AuthController mAuthController;
|
||||
private ViewGroup mIndicationArea;
|
||||
private KeyguardIndicationTextView mTopIndicationView;
|
||||
private KeyguardIndicationTextView mLockScreenIndicationView;
|
||||
@@ -201,6 +203,7 @@ public class KeyguardIndicationController {
|
||||
}
|
||||
}
|
||||
};
|
||||
private boolean mFaceLockedOutThisAuthSession;
|
||||
|
||||
/**
|
||||
* Creates a new KeyguardIndicationController and registers callbacks.
|
||||
@@ -221,6 +224,7 @@ public class KeyguardIndicationController {
|
||||
@Main DelayableExecutor executor,
|
||||
@Background DelayableExecutor bgExecutor,
|
||||
FalsingManager falsingManager,
|
||||
AuthController authController,
|
||||
LockPatternUtils lockPatternUtils,
|
||||
ScreenLifecycle screenLifecycle,
|
||||
KeyguardBypassController keyguardBypassController,
|
||||
@@ -240,6 +244,7 @@ public class KeyguardIndicationController {
|
||||
mExecutor = executor;
|
||||
mBackgroundExecutor = bgExecutor;
|
||||
mLockPatternUtils = lockPatternUtils;
|
||||
mAuthController = authController;
|
||||
mFalsingManager = falsingManager;
|
||||
mKeyguardBypassController = keyguardBypassController;
|
||||
mAccessibilityManager = accessibilityManager;
|
||||
@@ -755,7 +760,7 @@ public class KeyguardIndicationController {
|
||||
* logic.
|
||||
*/
|
||||
private void showBiometricMessage(CharSequence biometricMessage,
|
||||
CharSequence biometricMessageFollowUp) {
|
||||
@Nullable CharSequence biometricMessageFollowUp) {
|
||||
if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
|
||||
return;
|
||||
}
|
||||
@@ -1105,6 +1110,13 @@ public class KeyguardIndicationController {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
|
||||
if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
|
||||
mFaceLockedOutThisAuthSession = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricError(int msgId, String errString,
|
||||
BiometricSourceType biometricSourceType) {
|
||||
@@ -1124,8 +1136,10 @@ public class KeyguardIndicationController {
|
||||
}
|
||||
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
|
||||
handleFaceAuthTimeoutError(deferredFaceMessage);
|
||||
} else if (isLockoutError(msgId)) {
|
||||
handleFaceLockoutError(errString);
|
||||
} else {
|
||||
handleGenericBiometricError(errString);
|
||||
showErrorMessageNowOrLater(errString, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1134,7 +1148,7 @@ public class KeyguardIndicationController {
|
||||
debugLog("suppressingFingerprintError msgId=" + msgId
|
||||
+ " errString= " + errString);
|
||||
} else {
|
||||
handleGenericBiometricError(errString);
|
||||
showErrorMessageNowOrLater(errString, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1145,7 +1159,7 @@ public class KeyguardIndicationController {
|
||||
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
|
||||
// check of whether non-strong biometric is allowed
|
||||
return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
|
||||
&& msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
|
||||
&& !isLockoutError(msgId))
|
||||
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
|
||||
|| msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
|
||||
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
|
||||
@@ -1234,6 +1248,31 @@ public class KeyguardIndicationController {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFaceLockoutError(String errString) {
|
||||
int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
|
||||
: R.string.keyguard_unlock;
|
||||
String followupMessage = mContext.getString(followupMsgId);
|
||||
// Lockout error can happen multiple times in a session because we trigger face auth
|
||||
// even when it is locked out so that the user is aware that face unlock would have
|
||||
// triggered but didn't because it is locked out.
|
||||
|
||||
// On first lockout we show the error message from FaceManager, which tells the user they
|
||||
// had too many unsuccessful attempts.
|
||||
if (!mFaceLockedOutThisAuthSession) {
|
||||
mFaceLockedOutThisAuthSession = true;
|
||||
showErrorMessageNowOrLater(errString, followupMessage);
|
||||
} else if (!mAuthController.isUdfpsFingerDown()) {
|
||||
// On subsequent lockouts, we show a more generic locked out message.
|
||||
showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable),
|
||||
followupMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLockoutError(int msgId) {
|
||||
return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
|
||||
|| msgId == FaceManager.FACE_ERROR_LOCKOUT;
|
||||
}
|
||||
|
||||
private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
|
||||
debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
|
||||
if (canUnlockWithFingerprint()) {
|
||||
@@ -1274,11 +1313,11 @@ public class KeyguardIndicationController {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGenericBiometricError(String errString) {
|
||||
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
|
||||
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
|
||||
mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
|
||||
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
|
||||
showBiometricMessage(errString);
|
||||
showBiometricMessage(errString, followUpMsg);
|
||||
} else {
|
||||
mBiometricErrorMessageToShowOnScreenOn = errString;
|
||||
}
|
||||
|
||||
@@ -1621,7 +1621,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
|
||||
public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
|
||||
throws RemoteException {
|
||||
// Preconditions for face auth to run
|
||||
keyguardNotGoingAway();
|
||||
@@ -1638,7 +1638,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
|
||||
faceAuthLockedOut();
|
||||
mTestableLooper.processAllMessages();
|
||||
|
||||
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
|
||||
// This is needed beccause we want to show face locked out error message whenever face auth
|
||||
// is supposed to run.
|
||||
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,6 +20,7 @@ import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
|
||||
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
|
||||
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
|
||||
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
|
||||
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
|
||||
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
|
||||
|
||||
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
|
||||
@@ -54,6 +55,7 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
@@ -88,6 +90,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
|
||||
import com.android.settingslib.fuelgauge.BatteryStatus;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.biometrics.AuthController;
|
||||
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dock.DockManager;
|
||||
@@ -173,6 +176,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
|
||||
private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
|
||||
@Mock
|
||||
private ScreenLifecycle mScreenLifecycle;
|
||||
@Mock
|
||||
private AuthController mAuthController;
|
||||
@Captor
|
||||
private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
|
||||
@Captor
|
||||
@@ -263,8 +268,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
|
||||
mWakeLockBuilder,
|
||||
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
|
||||
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
|
||||
mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
|
||||
mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager,
|
||||
mUserManager, mExecutor, mExecutor, mFalsingManager,
|
||||
mAuthController, mLockPatternUtils, mScreenLifecycle,
|
||||
mKeyguardBypassController, mAccessibilityManager,
|
||||
mFaceHelpMessageDeferral);
|
||||
mController.init();
|
||||
mController.setIndicationArea(mIndicationArea);
|
||||
@@ -1371,6 +1377,110 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() {
|
||||
createController();
|
||||
onFaceLockoutError("first lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
|
||||
createController();
|
||||
fingerprintUnlockIsPossible();
|
||||
onFaceLockoutError("first lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
|
||||
mContext.getString(R.string.keyguard_suggest_fingerprint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() {
|
||||
createController();
|
||||
fingerprintUnlockIsNotPossible();
|
||||
onFaceLockoutError("first lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
|
||||
mContext.getString(R.string.keyguard_unlock));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() {
|
||||
createController();
|
||||
onFaceLockoutError("first lockout");
|
||||
clearInvocations(mRotateTextViewController);
|
||||
|
||||
onFaceLockoutError("second lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
|
||||
mContext.getString(R.string.keyguard_face_unlock_unavailable));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
|
||||
createController();
|
||||
onFaceLockoutError("first lockout");
|
||||
clearInvocations(mRotateTextViewController);
|
||||
|
||||
when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
|
||||
onFaceLockoutError("second lockout");
|
||||
|
||||
verifyNoMoreInteractions(mRotateTextViewController);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
|
||||
createController();
|
||||
fingerprintUnlockIsPossible();
|
||||
onFaceLockoutError("first lockout");
|
||||
clearInvocations(mRotateTextViewController);
|
||||
|
||||
onFaceLockoutError("second lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
|
||||
mContext.getString(R.string.keyguard_suggest_fingerprint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() {
|
||||
createController();
|
||||
fingerprintUnlockIsNotPossible();
|
||||
onFaceLockoutError("first lockout");
|
||||
clearInvocations(mRotateTextViewController);
|
||||
|
||||
onFaceLockoutError("second lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
|
||||
mContext.getString(R.string.keyguard_unlock));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() {
|
||||
createController();
|
||||
onFaceLockoutError("first lockout");
|
||||
clearInvocations(mRotateTextViewController);
|
||||
when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false);
|
||||
mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
|
||||
|
||||
onFaceLockoutError("second lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
|
||||
createController();
|
||||
onFaceLockoutError("first lockout");
|
||||
clearInvocations(mRotateTextViewController);
|
||||
when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
|
||||
mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
|
||||
|
||||
onFaceLockoutError("second lockout");
|
||||
|
||||
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
|
||||
mContext.getString(R.string.keyguard_face_unlock_unavailable));
|
||||
}
|
||||
|
||||
private void sendUpdateDisclosureBroadcast() {
|
||||
mBroadcastReceiver.onReceive(mContext, new Intent());
|
||||
@@ -1419,4 +1529,33 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
|
||||
anyObject(), anyBoolean());
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyIndicationShown(int indicationType, String message) {
|
||||
verify(mRotateTextViewController)
|
||||
.updateIndication(eq(indicationType),
|
||||
mKeyguardIndicationCaptor.capture(),
|
||||
eq(true));
|
||||
assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString())
|
||||
.isEqualTo(message);
|
||||
}
|
||||
|
||||
private void fingerprintUnlockIsNotPossible() {
|
||||
setupFingerprintUnlockPossible(false);
|
||||
}
|
||||
|
||||
private void fingerprintUnlockIsPossible() {
|
||||
setupFingerprintUnlockPossible(true);
|
||||
}
|
||||
|
||||
private void setupFingerprintUnlockPossible(boolean possible) {
|
||||
when(mKeyguardUpdateMonitor
|
||||
.getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser()))
|
||||
.thenReturn(possible);
|
||||
}
|
||||
|
||||
private void onFaceLockoutError(String errMsg) {
|
||||
mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
|
||||
errMsg,
|
||||
BiometricSourceType.FACE);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user