Show bouncer after failed UDFPS attempts

* Show the bouncer after 3 consecutive UDFPS failures
* Show the bouncer if the shade is expanded and UDFPS is in lockout
due to too many attempts
* Make sure to show the lockout reason bouncer prompt if there
have been too many attempts

Test: atest BiometricsUnlockControllerTest UdfpsKeyguardViewControllerTest StatusBarKeyguardViewManagerTest
Test: manual
Bug: 206996872
Change-Id: I9992acd121de5b422801228777e833043764379a
This commit is contained in:
Beverly
2021-11-19 12:56:50 -05:00
committed by Beverly Tai
parent 8dfb06f117
commit 98902d6319
10 changed files with 135 additions and 7 deletions

View File

@@ -801,13 +801,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}
boolean lockedOutStateChanged = false;
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
requireStrongAuthIfAllLockedOut();
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOut;
mFingerprintLockedOut = true;
if (isUdfpsEnrolled()) {
updateFingerprintListeningState();
@@ -820,9 +823,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
}
}
if (lockedOutStateChanged) {
notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
}
}
private void handleFingerprintLockoutReset() {
boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
mFingerprintLockedOut = false;
mFingerprintLockedOutPermanent = false;
@@ -837,6 +845,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
} else {
updateFingerprintListeningState();
}
if (changed) {
notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
}
}
private void setFingerprintRunningState(int fingerprintRunningState) {
@@ -999,7 +1011,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
boolean lockedOutStateChanged = false;
if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged = !mFaceLockedOutPermanent;
mFaceLockedOutPermanent = true;
requireStrongAuthIfAllLockedOut();
}
@@ -1011,11 +1025,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
BiometricSourceType.FACE);
}
}
if (lockedOutStateChanged) {
notifyLockedOutStateChanged(BiometricSourceType.FACE);
}
}
private void handleFaceLockoutReset() {
boolean changed = mFaceLockedOutPermanent;
mFaceLockedOutPermanent = false;
updateFaceListeningState();
if (changed) {
notifyLockedOutStateChanged(BiometricSourceType.FACE);
}
}
private void setFaceRunningState(int faceRunningState) {
@@ -1237,6 +1261,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
private void notifyLockedOutStateChanged(BiometricSourceType type) {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onLockedOutStateChanged(type);
}
}
}
public boolean isScreenOn() {
return mScreenOn;
}
@@ -2454,6 +2488,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
public boolean isFingerprintLockedOut() {
return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
}
/**
* If biometrics hardware is available, not disabled, and user has enrolled templates.
* This does NOT check if the device is encrypted or in lockdown.

View File

@@ -291,6 +291,11 @@ public class KeyguardUpdateMonitorCallback {
*/
public void onStrongAuthStateChanged(int userId) { }
/**
* When the current user's locked out state changed.
*/
public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { }
/**
* Called when the dream's window state is changed.
* @param dreaming true if the dream's window has been created and is visible

View File

@@ -255,7 +255,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
private void maybeShowInputBouncer() {
if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
mKeyguardViewManager.showBouncer(true);
mKeyguardViewManager.resetAlternateAuth(false);
}
}

View File

@@ -793,7 +793,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
} else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
} else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) {
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
} else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;

View File

@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.PowerManager;
@@ -46,9 +47,11 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
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.policy.KeyguardStateController;
import java.io.FileDescriptor;
@@ -71,6 +74,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 = 3;
@IntDef(prefix = { "MODE_" }, value = {
MODE_NONE,
@@ -167,6 +171,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
private static final class PendingAuthenticated {
public final int userId;
@@ -209,7 +217,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
BIOMETRIC_IRIS_FAILURE(403),
@UiEvent(doc = "A biometric event of type iris errored.")
BIOMETRIC_IRIS_ERROR(404);
BIOMETRIC_IRIS_ERROR(404),
@UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.")
BIOMETRIC_BOUNCER_SHOWN(916);
private final int mId;
@@ -257,7 +268,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
NotificationMediaManager notificationMediaManager,
WakefulnessLifecycle wakefulnessLifecycle,
ScreenLifecycle screenLifecycle,
AuthController authController) {
AuthController authController,
StatusBarStateController statusBarStateController) {
mContext = context;
mPowerManager = powerManager;
mShadeController = shadeController;
@@ -279,6 +291,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mKeyguardBypassController.setUnlockController(this);
mMetricsLogger = metricsLogger;
mAuthController = authController;
mStatusBarStateController = statusBarStateController;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -620,6 +633,22 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
.setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
.ifPresent(UI_EVENT_LOGGER::log);
long currUptimeMillis = SystemClock.uptimeMillis();
if (currUptimeMillis - mLastFpFailureUptimeMillis < 2000) { // attempt within 2 seconds
mNumConsecutiveFpFailures += 1;
} else {
mNumConsecutiveFpFailures = 1;
}
mLastFpFailureUptimeMillis = currUptimeMillis;
if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT)
&& mUpdateMonitor.isUdfpsSupported()
&& mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
mKeyguardViewController.showBouncer(true);
UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
mNumConsecutiveFpFailures = 0;
}
cleanup();
}
@@ -631,6 +660,16 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
.addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
.ifPresent(UI_EVENT_LOGGER::log);
// if we're on the shade and we're locked out, immediately show the bouncer
if (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)) {
mKeyguardViewController.showBouncer(true);
}
cleanup();
}
@@ -664,6 +703,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mBiometricModeListener.onResetMode();
mBiometricModeListener.notifyBiometricAuthModeChanged();
}
mNumConsecutiveFpFailures = 0;
mLastFpFailureUptimeMillis = 0;
}
@VisibleForTesting

View File

@@ -21,6 +21,7 @@ import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import android.content.Context;
import android.content.res.ColorStateList;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,6 +82,13 @@ public class KeyguardBouncer {
public void onStrongAuthStateChanged(int userId) {
mBouncerPromptReason = mCallback.getBouncerPromptReason();
}
@Override
public void onLockedOutStateChanged(BiometricSourceType type) {
if (type == BiometricSourceType.FINGERPRINT) {
mBouncerPromptReason = mCallback.getBouncerPromptReason();
}
}
};
private final Runnable mRemoveViewRunnable = this::removeView;
private final KeyguardBypassController mKeyguardBypassController;

View File

@@ -441,6 +441,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
*/
public void showBouncer(boolean scrimmed) {
resetAlternateAuth(false);
if (mShowing && !mBouncer.isShowing()) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
}

View File

@@ -351,9 +351,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
mSystemClock.advanceTime(205);
mController.onTouchOutsideView();
// THEN show the bouncer and reset alt auth
// THEN show the bouncer
verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
}
@Test

View File

@@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -101,6 +102,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private ScreenLifecycle mScreenLifecycle;
@Mock
private StatusBarStateController mStatusBarStateController;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -123,7 +126,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController);
mAuthController, mStatusBarStateController);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
}
@@ -377,6 +380,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
.isEqualTo(BiometricUnlockController.MODE_ONLY_WAKE);
}
@Test
public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
// GIVEN UDFPS is supported
when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
// WHEN udfps fails twice - then don't show the bouncer
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
// WHEN udfps fails the third time
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
// THEN show the bouncer
verify(mStatusBarKeyguardViewManager).showBouncer(true);
}
@Test
public void onFinishedGoingToSleep_authenticatesWhenPending() {
when(mUpdateMonitor.isGoingToSleep()).thenReturn(true);

View File

@@ -373,6 +373,21 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
}
@Test
public void testHideAltAuth_onShowBouncer() {
// GIVEN alt auth is showing
mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
when(mBouncer.isShowing()).thenReturn(false);
when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
reset(mAlternateAuthInterceptor);
// WHEN showBouncer is called
mStatusBarKeyguardViewManager.showBouncer(true);
// THEN alt bouncer should be hidden
verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer();
}
@Test
public void testUpdateResources_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateResources();