RESTRICT AUTOMERGE Fix detect biometric logic.

Reverts the revert: Iea66d7782b3365dea827b854783aeddaa5c6699c
and updates the detect biometric logic to forward fix the CUJ.

[1] Never allow detectFingerprint or detectFace
to enter the device. DetectBiometric at most can
bring up the bouncer. It will never run the authenticate
path.

[2] When the temporary lockout state is reset, reset the fingerprint
listening state so that if SysUI was running detection,
SysUI will subsequently switch over to running authenticate.
If there's faceDetect running when lockout resets, we allow
faceDetect to complete running - if successful, it'll bring up the
bouncer where the user can either enter pin/pattern/password to
enter the device, or attempt face auth again by tapping the bouncer.

[3] Not directly related to detect but was causing a stuck
BIOMETRIC_CANCELLING_RESTARTING state: make sure to update
the biometric running state if the cancellation signal times out
and the biometric running state was BIOMETRIC_CANCELLING_RESTARTING.
Previously, the biometric state was only STOPPED when the cancellation
signal (framework didn't get back to SysUI in time) timed out

Note: There's a bug b/269743883 in the detect CUJ where if its
requested right after being cancelled, it can be stuck in the
CANCELLING_RESTARTING state. However, this will timeout after
3000ms and restart the fingerprint listening state afterwards.
This will be an issue until the framework bug is resolved to
send the cancellation confirmation to SysUI.

Test: atest KeyguardUpdateMonitorTest BiometricsUnlockControllerTest
Test: after the lockout, putting any finger on the FP sensor
will bring up the bouncer. after the lockout counter expires,
putting an unenrolled finger on the FP sensor will result in a FP error.
only enrolled fingerprints will allow the user to enter the device.
Bug: 269574598
Bug: 269769293
Bug: 269200614

Change-Id: Ic8f3d5b8b6a898a6d22f69c6e2ed4a3c24c61a6b
This commit is contained in:
Beverly
2023-02-16 19:21:17 +00:00
committed by Beverly Tai
parent 1a10f037a4
commit 5cfa927141
7 changed files with 196 additions and 85 deletions

View File

@@ -816,6 +816,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
private void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Assert.isMainThread();
Trace.beginSection("KeyGuardUpdateMonitor#onBiometricDetected");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricDetected(userId, biometricSourceType, isStrongBiometric);
}
}
Trace.endSection();
}
@VisibleForTesting
protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
Assert.isMainThread();
@@ -892,6 +905,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
private void handleBiometricDetected(int authUserId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerBiometricDetected");
onBiometricDetected(authUserId, biometricSourceType, isStrongBiometric);
if (biometricSourceType == FINGERPRINT) {
mLogger.logFingerprintDetected(authUserId, isStrongBiometric);
} else if (biometricSourceType == FACE) {
mLogger.logFaceDetected(authUserId, isStrongBiometric);
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
}
Trace.endSection();
}
private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
@@ -943,8 +970,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void onFingerprintCancelNotReceived() {
mLogger.e("Fp cancellation not received, transitioning to STOPPED");
final boolean wasCancellingRestarting = mFingerprintRunningState
== BIOMETRIC_STATE_CANCELLING_RESTARTING;
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
if (wasCancellingRestarting) {
KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else {
KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
}
}
private void handleFingerprintError(int msgId, String errString) {
@@ -1031,6 +1064,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
() -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE),
getBiometricLockoutDelay());
} else {
boolean temporaryLockoutReset = wasLockout && !mFingerprintLockedOut;
if (temporaryLockoutReset) {
mLogger.d("temporaryLockoutReset - stopListeningForFingerprint() to stop"
+ " detectFingerprint");
stopListeningForFingerprint();
}
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -1730,10 +1769,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
};
private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback =
(sensorId, userId, isStrongBiometric) -> {
// Trigger the fingerprint detected path so the bouncer can be shown
handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric);
};
private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
= (sensorId, userId, isStrongBiometric) -> {
// Trigger the face success path so the bouncer can be shown
handleFaceAuthenticated(userId, isStrongBiometric);
// Trigger the face detected path so the bouncer can be shown
handleBiometricDetected(userId, FACE, isStrongBiometric);
};
@VisibleForTesting
@@ -2765,8 +2810,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState
&& shouldListenSideFpsState
&& !isFingerprintLockedOut();
&& shouldListenSideFpsState;
logListenerModelData(
new KeyguardFingerprintListenModel(
System.currentTimeMillis(),
@@ -2825,8 +2869,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection
&& canBypass && !mPrimaryBouncerIsOrWillBeShowing
&& !isUserInLockdown(user)
&& !isFingerprintLockedOut();
&& !isUserInLockdown(user);
final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
// If the face or fp has recently been authenticated do not attempt to authenticate again.
@@ -2922,11 +2965,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLogger.v("startListeningForFingerprint - detect");
mFpm.detectFingerprint(
mFingerprintCancelSignal,
(sensorId, user, isStrongBiometric) -> {
mLogger.d("fingerprint detected");
// Trigger the fingerprint success path so the bouncer can be shown
handleFingerprintAuthenticated(user, isStrongBiometric);
},
mFingerprintDetectionCallback,
userId);
} else {
mLogger.v("startListeningForFingerprint - authenticate");

View File

@@ -214,13 +214,21 @@ public class KeyguardUpdateMonitorCallback {
public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { }
/**
* Called when a biometric is recognized.
* Called when a biometric is authenticated.
* @param userId the user id for which the biometric sample was authenticated
* @param biometricSourceType
*/
public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) { }
/**
* Called when a biometric is detected but not successfully authenticated.
* @param userId the user id for which the biometric sample was detected
* @param biometricSourceType
*/
public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) { }
/**
* Called when biometric authentication provides help string (e.g. "Try again")
* @param msgId

View File

@@ -167,6 +167,20 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
}
fun logFaceDetected(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(TAG, DEBUG, {
int1 = userId
bool1 = isStrongBiometric
}, {"Face detected: userId: $int1, isStrongBiometric: $bool1"})
}
fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(TAG, DEBUG, {
int1 = userId
bool1 = isStrongBiometric
}, {"Fingerprint detected: userId: $int1, isStrongBiometric: $bool1"})
}
fun logFingerprintError(msgId: Int, originalErrMsg: String) {
logBuffer.log(TAG, DEBUG, {
str1 = originalErrMsg

View File

@@ -382,6 +382,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
Trace.endSection();
}
@Override
public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricDetected");
if (mUpdateMonitor.isGoingToSleep()) {
Trace.endSection();
return;
}
startWakeAndUnlock(MODE_SHOW_BOUNCER);
}
@Override
public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {

View File

@@ -29,7 +29,6 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -622,19 +621,47 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
// Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
// will trigger updateBiometricListeningState();
clearInvocations(mFingerprintManager);
mKeyguardUpdateMonitor.resetBiometricListeningState();
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
givenDetectFingerprintWithClearingFingerprintManagerInvocations();
verifyFingerprintAuthenticateNeverCalled();
verifyFingerprintDetectCall();
}
@Test
public void whenDetectFingerprint_biometricDetectCallback() {
ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
givenDetectFingerprintWithClearingFingerprintManagerInvocations();
verify(mFingerprintManager).detectFingerprint(
any(), fpDetectCallbackCaptor.capture(), anyInt());
fpDetectCallbackCaptor.getValue().onFingerprintDetected(0, 0, true);
// THEN verify keyguardUpdateMonitorCallback receives a detect callback
// and NO authenticate callbacks
verify(mTestCallback).onBiometricDetected(
eq(0), eq(BiometricSourceType.FINGERPRINT), eq(true));
verify(mTestCallback, never()).onBiometricAuthenticated(
anyInt(), any(), anyBoolean());
}
@Test
public void whenDetectFace_biometricDetectCallback() {
ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor =
ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class);
givenDetectFace();
verify(mFaceManager).detectFace(any(), faceDetectCallbackCaptor.capture(), anyInt());
faceDetectCallbackCaptor.getValue().onFaceDetected(0, 0, false);
// THEN verify keyguardUpdateMonitorCallback receives a detect callback
// and NO authenticate callbacks
verify(mTestCallback).onBiometricDetected(
eq(0), eq(BiometricSourceType.FACE), eq(false));
verify(mTestCallback, never()).onBiometricAuthenticated(
anyInt(), any(), anyBoolean());
}
@Test
public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
// GIVEN unlocking with biometric is allowed
@@ -663,7 +690,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
strongAuthNotRequired();
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
fingerprintErrorTemporaryLockedOut();
// THEN unlocking with face is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -686,7 +713,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
strongAuthNotRequired();
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
fingerprintErrorTemporaryLockedOut();
// THEN unlocking with fingerprint is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -710,7 +737,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
fingerprintErrorTemporaryLockedOut();
// THEN user is NOT considered as "having trust" and bouncer cannot be skipped
Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
@@ -772,11 +799,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
// GIVEN mocked keyguardUpdateMonitorCallback
KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback =
mock(KeyguardUpdateMonitorCallback.class);
mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
// GIVEN bypass is enabled, face detection is supported
lockscreenBypassIsAllowed();
supportsFaceDetection();
@@ -795,22 +817,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verifyFaceAuthenticateNeverCalled();
// THEN biometric help message sent to callback
verify(keyguardUpdateMonitorCallback).onBiometricHelp(
verify(mTestCallback).onBiometricHelp(
eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE));
}
@Test
public void faceDetect_whenStrongAuthRequiredAndBypass() {
// GIVEN bypass is enabled, face detection is supported and strong auth is required
lockscreenBypassIsAllowed();
supportsFaceDetection();
strongAuthRequiredEncrypted();
keyguardIsVisible();
// fingerprint is NOT running, UDFPS is NOT supported
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
givenDetectFace();
// FACE detect is triggered, not authenticate
verifyFaceDetectCall();
@@ -826,39 +839,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verifyFaceDetectNeverCalled();
}
@Test
public void noFaceRun_whenFpLockout() {
// GIVEN bypass is enabled, face detection is supported and strong auth is required
lockscreenBypassIsAllowed();
supportsFaceDetection();
strongAuthRequiredEncrypted();
keyguardIsVisible();
// fingerprint is NOT running, UDFPS is NOT supported
// GIVEN fp is locked out
when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), anyInt()))
.thenReturn(BIOMETRIC_LOCKOUT_TIMED);
mKeyguardUpdateMonitor.handleUserSwitchComplete(0);
assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(true);
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
// FACE detect is NOT triggered and face authenticate is NOT triggered
verifyFaceDetectNeverCalled();
verifyFaceAuthenticateNeverCalled();
// WHEN bouncer becomes visible
setKeyguardBouncerVisibility(true);
clearInvocations(mFaceManager);
// THEN face scanning is not run
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verifyFaceAuthenticateNeverCalled();
verifyFaceDetectNeverCalled();
}
@Test
public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
// GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
@@ -1177,8 +1157,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// Fingerprint should be cancelled on lockout if going to lockout state, else
// restarted if it's not
assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
.isEqualTo(fpLocked
? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING);
.isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
}
@Test
@@ -1637,7 +1616,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
// Fingerprint is locked out.
fingerprintErrorLockedOut();
fingerprintErrorTemporaryLockedOut();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
@@ -2446,6 +2425,29 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
eq(false));
}
@Test
public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() {
ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor =
ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class);
verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture());
// GIVEN device is locked out
fingerprintErrorTemporaryLockedOut();
// GIVEN FP detection is running
givenDetectFingerprintWithClearingFingerprintManagerInvocations();
verifyFingerprintDetectCall();
verifyFingerprintAuthenticateNeverCalled();
// WHEN temporary lockout resets
fpLockoutResetCallbackCaptor.getValue().onLockoutReset(0);
mTestableLooper.processAllMessages();
// THEN fingerprint detect state should cancel & then restart (for authenticate call)
assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
.isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
}
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyInt(), anyInt());
@@ -2550,7 +2552,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mKeyguardUpdateMonitor.setSwitchingUser(true);
}
private void fingerprintErrorLockedOut() {
private void fingerprintErrorTemporaryLockedOut() {
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
}
@@ -2690,6 +2692,30 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
receiver.setPendingResult(pendingResult);
}
private void givenDetectFingerprintWithClearingFingerprintManagerInvocations() {
// Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
// will trigger updateBiometricListeningState();
clearInvocations(mFingerprintManager);
mKeyguardUpdateMonitor.resetBiometricListeningState();
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
}
private void givenDetectFace() {
// GIVEN bypass is enabled, face detection is supported and strong auth is required
lockscreenBypassIsAllowed();
supportsFaceDetection();
strongAuthRequiredEncrypted();
keyguardIsVisible();
// fingerprint is NOT running, UDFPS is NOT supported
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
}
private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
int subscription = simInited
? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE;

View File

@@ -537,6 +537,26 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
verify(mVibratorHelper).vibrateAuthError(anyString());
}
@Test
public void onFingerprintDetect_showBouncer() {
// WHEN fingerprint detect occurs
mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
// THEN shows primary bouncer
verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
}
@Test
public void onFaceDetect_showBouncer() {
// WHEN face detect occurs
mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
BiometricSourceType.FACE, false /* isStrongBiometric */);
// THEN shows primary bouncer
verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
}
private void givenFingerprintModeUnlockCollapsing() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);

View File

@@ -452,13 +452,6 @@ public class FingerprintService extends SystemService {
return -1;
}
if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
// If this happens, something in KeyguardUpdateMonitor is wrong. This should only
// ever be invoked when the user is encrypted or lockdown.
Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
return -1;
}
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");