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

Commit 5cfa9271 authored by Beverly's avatar Beverly Committed by Beverly Tai
Browse files

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
parent 1a10f037
Loading
Loading
Loading
Loading
+51 −12
Original line number Diff line number Diff line
@@ -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,9 +970,15 @@ 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;
        if (wasCancellingRestarting) {
            KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
        } else {
            KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
        }
    }

    private void handleFingerprintError(int msgId, String errString) {
        Assert.isMainThread();
@@ -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");
+9 −1
Original line number Diff line number Diff line
@@ -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
+14 −0
Original line number Diff line number Diff line
@@ -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
+11 −0
Original line number Diff line number Diff line
@@ -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) {
+91 −65
Original line number Diff line number Diff line
@@ -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;
Loading