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:
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user