From 3905c8efac36381ab804fe2a0082068f8e532652 Mon Sep 17 00:00:00 2001 From: Beverly Date: Fri, 19 Aug 2022 19:31:03 +0000 Subject: [PATCH] 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 --- .../keyguard/KeyguardUpdateMonitor.java | 3 +- .../KeyguardIndicationController.java | 12 ++++++- .../phone/BiometricUnlockController.java | 22 ++++++------- .../KeyguardIndicationControllerTest.java | 33 +++++++++++++++++++ .../phone/BiometricsUnlockControllerTest.java | 29 ++++++++-------- 5 files changed, 73 insertions(+), 26 deletions(-) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 6976e3e772deb..167d8af5ce795 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -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; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index c98364473f714..87f8a03098f52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -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); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 65ba5adddd60b..4e4fcda52d1c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -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()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 8473c377da618..6afadcf3bd426 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -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 mKeyguardUpdateMonitorCallbackCaptor; @Captor private ArgumentCaptor mKeyguardStateControllerCallbackCaptor; + @Captor + private ArgumentCaptor 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(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index d2680eb5d8bb0..a6808e01a0812 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -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()); } }