summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java143
6 files changed, 206 insertions, 15 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a7f8cb6f695e..c88da1895e8d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -403,6 +403,8 @@
<string name="keyguard_face_failed">Can\u2019t recognize face</string>
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
+ <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] -->
+ <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 558869c46373..1bb83f44ce6c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -798,7 +798,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mFingerprintCancelSignal = null;
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_FP_AUTHENTICATED);
- mLogger.d("onFingerprintAuthenticated");
+ mLogger.logFingerprintSuccess(userId, isStrongBiometric);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2716,7 +2716,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
// TODO: always disallow when fp is already locked out?
- final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+ final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
@@ -2745,7 +2745,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean faceDisabledForUser = isFaceDisabled(user);
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
- final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2763,7 +2762,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& !faceAuthenticated
&& !mGoingToSleep
- && !fpOrFaceIsLockedOut;
+ // We only care about fp locked out state and not face because we still trigger
+ // face auth even when face is locked out to show the user a message that face
+ // unlock was supposed to run but didn't
+ && !fpLockedOut;
// Aggregate relevant fields for debug logging.
maybeLogListenerModelData(
@@ -2778,7 +2780,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
faceAuthenticated,
faceDisabledForUser,
isFaceLockedOut(),
- fpLockedout,
+ fpLockedOut,
mGoingToSleep,
awakeKeyguard,
mKeyguardGoingAway,
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 3308f5550bfc..6276142d605f 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -153,6 +153,13 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "fingerprintRunningState: $int1" })
}
+ fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = userId
+ bool1 = isStrongBiometric
+ }, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
+ }
+
fun logInvalidSubId(subId: Int) {
logBuffer.log(TAG, INFO,
{ int1 = subId },
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5d8232e24fe8..f2b86035200b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -79,6 +79,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -137,6 +138,7 @@ public class KeyguardIndicationController {
private final KeyguardStateController mKeyguardStateController;
protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final AuthController mAuthController;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -201,6 +203,7 @@ public class KeyguardIndicationController {
}
}
};
+ private boolean mFaceLockedOutThisAuthSession;
/**
* Creates a new KeyguardIndicationController and registers callbacks.
@@ -221,6 +224,7 @@ public class KeyguardIndicationController {
@Main DelayableExecutor executor,
@Background DelayableExecutor bgExecutor,
FalsingManager falsingManager,
+ AuthController authController,
LockPatternUtils lockPatternUtils,
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
@@ -240,6 +244,7 @@ public class KeyguardIndicationController {
mExecutor = executor;
mBackgroundExecutor = bgExecutor;
mLockPatternUtils = lockPatternUtils;
+ mAuthController = authController;
mFalsingManager = falsingManager;
mKeyguardBypassController = keyguardBypassController;
mAccessibilityManager = accessibilityManager;
@@ -755,7 +760,7 @@ public class KeyguardIndicationController {
* logic.
*/
private void showBiometricMessage(CharSequence biometricMessage,
- CharSequence biometricMessageFollowUp) {
+ @Nullable CharSequence biometricMessageFollowUp) {
if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
return;
}
@@ -1106,6 +1111,13 @@ public class KeyguardIndicationController {
}
@Override
+ public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
+ mFaceLockedOutThisAuthSession = false;
+ }
+ }
+
+ @Override
public void onBiometricError(int msgId, String errString,
BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE) {
@@ -1124,8 +1136,10 @@ public class KeyguardIndicationController {
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
handleFaceAuthTimeoutError(deferredFaceMessage);
+ } else if (isLockoutError(msgId)) {
+ handleFaceLockoutError(errString);
} else {
- handleGenericBiometricError(errString);
+ showErrorMessageNowOrLater(errString, null);
}
}
@@ -1134,7 +1148,7 @@ public class KeyguardIndicationController {
debugLog("suppressingFingerprintError msgId=" + msgId
+ " errString= " + errString);
} else {
- handleGenericBiometricError(errString);
+ showErrorMessageNowOrLater(errString, null);
}
}
@@ -1145,7 +1159,7 @@ public class KeyguardIndicationController {
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
// check of whether non-strong biometric is allowed
return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
- && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+ && !isLockoutError(msgId))
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
|| msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
@@ -1234,6 +1248,31 @@ public class KeyguardIndicationController {
}
}
+ private void handleFaceLockoutError(String errString) {
+ int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+ : R.string.keyguard_unlock;
+ String followupMessage = mContext.getString(followupMsgId);
+ // Lockout error can happen multiple times in a session because we trigger face auth
+ // even when it is locked out so that the user is aware that face unlock would have
+ // triggered but didn't because it is locked out.
+
+ // On first lockout we show the error message from FaceManager, which tells the user they
+ // had too many unsuccessful attempts.
+ if (!mFaceLockedOutThisAuthSession) {
+ mFaceLockedOutThisAuthSession = true;
+ showErrorMessageNowOrLater(errString, followupMessage);
+ } else if (!mAuthController.isUdfpsFingerDown()) {
+ // On subsequent lockouts, we show a more generic locked out message.
+ showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable),
+ followupMessage);
+ }
+ }
+
+ private static boolean isLockoutError(int msgId) {
+ return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+ || msgId == FaceManager.FACE_ERROR_LOCKOUT;
+ }
+
private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
if (canUnlockWithFingerprint()) {
@@ -1274,11 +1313,11 @@ public class KeyguardIndicationController {
}
}
- private void handleGenericBiometricError(String errString) {
+ private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
- showBiometricMessage(errString);
+ showBiometricMessage(errString, followUpMsg);
} else {
mBiometricErrorMessageToShowOnScreenOn = errString;
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a8284d29197d..9c14ee610b25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1621,7 +1621,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
+ public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
throws RemoteException {
// Preconditions for face auth to run
keyguardNotGoingAway();
@@ -1638,7 +1638,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
faceAuthLockedOut();
mTestableLooper.processAllMessages();
- assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ // This is needed beccause we want to show face locked out error message whenever face auth
+ // is supposed to run.
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
}
@Test
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 9a13e93ebcfc..9b17cc296081 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 android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
@@ -54,6 +55,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
@@ -88,6 +90,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
@@ -173,6 +176,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
@Mock
private ScreenLifecycle mScreenLifecycle;
+ @Mock
+ private AuthController mAuthController;
@Captor
private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
@Captor
@@ -263,8 +268,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mWakeLockBuilder,
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
- mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
- mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager,
+ mUserManager, mExecutor, mExecutor, mFalsingManager,
+ mAuthController, mLockPatternUtils, mScreenLifecycle,
+ mKeyguardBypassController, mAccessibilityManager,
mFaceHelpMessageDeferral);
mController.init();
mController.setIndicationArea(mIndicationArea);
@@ -1371,6 +1377,110 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
+ @Test
+ public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout");
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
+ createController();
+ fingerprintUnlockIsPossible();
+ onFaceLockoutError("first lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() {
+ createController();
+ fingerprintUnlockIsNotPossible();
+ onFaceLockoutError("first lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_face_unlock_unavailable));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
+ onFaceLockoutError("second lockout");
+
+ verifyNoMoreInteractions(mRotateTextViewController);
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
+ createController();
+ fingerprintUnlockIsPossible();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() {
+ createController();
+ fingerprintUnlockIsNotPossible();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+ when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false);
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout");
+ }
+
+ @Test
+ public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+ when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_face_unlock_unavailable));
+ }
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
@@ -1419,4 +1529,33 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
anyObject(), anyBoolean());
}
}
+
+ private void verifyIndicationShown(int indicationType, String message) {
+ verify(mRotateTextViewController)
+ .updateIndication(eq(indicationType),
+ mKeyguardIndicationCaptor.capture(),
+ eq(true));
+ assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString())
+ .isEqualTo(message);
+ }
+
+ private void fingerprintUnlockIsNotPossible() {
+ setupFingerprintUnlockPossible(false);
+ }
+
+ private void fingerprintUnlockIsPossible() {
+ setupFingerprintUnlockPossible(true);
+ }
+
+ private void setupFingerprintUnlockPossible(boolean possible) {
+ when(mKeyguardUpdateMonitor
+ .getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(possible);
+ }
+
+ private void onFaceLockoutError(String errMsg) {
+ mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
+ errMsg,
+ BiometricSourceType.FACE);
+ }
}