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
This commit is contained in:
Beverly
2022-08-19 19:31:03 +00:00
parent 9dce321437
commit 3905c8efac
5 changed files with 73 additions and 26 deletions

View File

@@ -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;
/**

View File

@@ -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);
}
}

View File

@@ -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());
}

View File

@@ -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();

View File

@@ -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());
}
}