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

Commit 3905c8ef authored by Beverly's avatar Beverly
Browse files

Update FP failure bouncer & messaging logic

On devices that don't support haptics, when a fingeprint
fails, bring users to the lock screen (instead of bouncer).
Make sure the fingeprint error message will surface once the screen
is on.

In general, show the bouncer on the last attempt that causes a
fingerprint or face lockout event.

Update the UDPFS show-bouncer logic to after 3 consecutive attempts
(instead of 2).

Test: atest BiometricsUnlockControllerTest
Test: atest KeyguardIndicationControllerTest
Fixes: 240381698
Fixes: 242588598
Change-Id: I1ef7be8cdc9abae2cc2f4a9d361fcfb875be1dad
parent 9dce3214
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -206,7 +206,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
     */
    private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;

    private static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
    @VisibleForTesting
    public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
    public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;

    /**
+11 −1
Original line number Diff line number Diff line
@@ -122,6 +122,7 @@ public class KeyguardIndicationController {
    private static final int MSG_HIDE_TRANSIENT = 1;
    private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
    private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3;
    private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 4;
    private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
    public static final long DEFAULT_HIDE_DELAY_MS =
            3500 + KeyguardIndicationTextView.Y_IN_DURATION;
@@ -188,9 +189,11 @@ public class KeyguardIndicationController {
                }
            };
    private ScreenLifecycle mScreenLifecycle;
    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
    private final ScreenLifecycle.Observer mScreenObserver =
            new ScreenLifecycle.Observer() {
        @Override
        public void onScreenTurnedOn() {
            mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
            if (mBiometricErrorMessageToShowOnScreenOn != null) {
                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
                // We want to keep this message around in case the screen was off
@@ -259,6 +262,8 @@ public class KeyguardIndicationController {
                    showActionToUnlock();
                } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
                    hideBiometricMessage();
                } else if (msg.what == MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON) {
                    mBiometricErrorMessageToShowOnScreenOn = null;
                }
            }
        };
@@ -1060,6 +1065,11 @@ public class KeyguardIndicationController {
            } else if (showActionToUnlock) {
                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
                        TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
            } else {
                mBiometricErrorMessageToShowOnScreenOn = helpString;
                mHandler.sendMessageDelayed(
                        mHandler.obtainMessage(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON),
                        1000);
            }
        }

+11 −11
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.os.Handler;
@@ -64,7 +65,6 @@ import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;

@@ -87,7 +87,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
    private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
    private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
    private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 2;
    private static final int UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
    private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
            VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
    private static final VibrationEffect ERROR_VIBRATION_EFFECT =
@@ -450,7 +450,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
        // During wake and unlock, we need to draw black before waking up to avoid abrupt
        // brightness changes due to display state transitions.
        Runnable wakeUp = ()-> {
            if (!wasDeviceInteractive) {
            if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
                if (DEBUG_BIO_WAKELOCK) {
                    Log.i(TAG, "bio wakelock: Authenticated, waking up...");
                }
@@ -661,7 +661,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp

        if (!mVibratorHelper.hasVibrator()
                && (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
            startWakeAndUnlock(MODE_SHOW_BOUNCER);
            startWakeAndUnlock(MODE_ONLY_WAKE);
        } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
                && mUpdateMonitor.isUdfpsSupported()) {
            long currUptimeMillis = SystemClock.uptimeMillis();
@@ -672,7 +672,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
            }
            mLastFpFailureUptimeMillis = currUptimeMillis;

            if (mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
            if (mNumConsecutiveFpFailures >= UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
                startWakeAndUnlock(MODE_SHOW_BOUNCER);
                UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
                mNumConsecutiveFpFailures = 0;
@@ -698,13 +698,13 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
        Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
                .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));

        // if we're on the shade and we're locked out, immediately show the bouncer
        if (biometricSourceType == BiometricSourceType.FINGERPRINT
        final boolean fingerprintLockout = biometricSourceType == BiometricSourceType.FINGERPRINT
                && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
                || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
                && mUpdateMonitor.isUdfpsSupported()
                && (mStatusBarStateController.getState() == StatusBarState.SHADE
                    || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
                || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
        final boolean faceLockout = biometricSourceType == BiometricSourceType.FACE
                && (msgId == FaceManager.FACE_ERROR_LOCKOUT
                || msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT);
        if (fingerprintLockout || faceLockout) {
            startWakeAndUnlock(MODE_SHOW_BOUNCER);
            UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
        }
+33 −0
Original line number Diff line number Diff line
@@ -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 com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -31,6 +32,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;

import static com.google.common.truth.Truth.assertThat;
@@ -178,9 +180,12 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
    @Captor
    private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
    @Captor
    private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
    private KeyguardStateController.Callback mKeyguardStateControllerCallback;
    private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
    private StatusBarStateController.StateListener mStatusBarStateListener;
    private ScreenLifecycle.Observer mScreenObserver;
    private BroadcastReceiver mBroadcastReceiver;
    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
    private TestableLooper mTestableLooper;
@@ -273,6 +278,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
                mKeyguardUpdateMonitorCallbackCaptor.capture());
        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();

        verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
        mScreenObserver = mScreenObserverCaptor.getValue();

        mExecutor.runAllReady();
        reset(mRotateTextViewController);
    }
@@ -536,6 +544,31 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
        assertThat(mTextView.getText()).isNotEqualTo(message);
    }

    @Test
    public void transientIndication_visibleWhenWokenUp() {
        createController();
        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
        final String message = "helpMsg";

        // GIVEN screen is off
        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_OFF);

        // WHEN fingeprint help message received
        mController.setVisible(true);
        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
                message, BiometricSourceType.FINGERPRINT);

        // THEN message isn't shown right away
        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);

        // WHEN the screen turns on
        mScreenObserver.onScreenTurnedOn();
        mTestableLooper.processAllMessages();

        // THEN the message is shown
        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
    }

    @Test
    public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
        createController();
+16 −13
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -395,15 +397,19 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
    }

    @Test
    public void onUdfpsConsecutivelyFailedTwoTimes_showBouncer() {
    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
        // GIVEN UDFPS is supported
        when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);

        // WHEN udfps fails once - then don't show the bouncer
        // WHEN udfps fails once - then don't show the bouncer yet
        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());

        // WHEN udfps fails the second time
        // WHEN udfps fails the second time - then don't show the bouncer yet
        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());

        // WHEN udpfs fails the third time
        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);

        // THEN show the bouncer
@@ -427,8 +433,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
    }

    @Test
    public void onFPFailureNoHaptics_notDeviceInteractive_showBouncer() {
        // GIVEN no vibrator and the screen is off
    public void onFPFailureNoHaptics_notInteractive_showLockScreen() {
        // GIVEN no vibrator and device is dreaming
        when(mVibratorHelper.hasVibrator()).thenReturn(false);
        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
        when(mUpdateMonitor.isDreaming()).thenReturn(false);
@@ -436,15 +442,12 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
        // WHEN FP fails
        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);

        // after device is finished waking up
        mBiometricUnlockController.mWakefulnessObserver.onFinishedWakingUp();

        // THEN show the bouncer
        verify(mStatusBarKeyguardViewManager).showBouncer(true);
        // THEN wakeup the device
        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
    }

    @Test
    public void onFPFailureNoHaptics_dreaming_showBouncer() {
    public void onFPFailureNoHaptics_dreaming_showLockScreen() {
        // GIVEN no vibrator and device is dreaming
        when(mVibratorHelper.hasVibrator()).thenReturn(false);
        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -453,7 +456,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
        // WHEN FP fails
        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);

        // THEN show the bouncer
        verify(mStatusBarKeyguardViewManager).showBouncer(true);
        // THEN wakeup the device
        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
    }
}