diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 947dc2de984170596feba5ebcf91f56d7f1bfbca..ccd0dccd13647e1bb058b5bf41b8002b09d2ffeb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1970,8 +1970,8 @@
Face Unlock canceled by user
Too many attempts. Try again later.
-
- Too many attempts. Face Unlock disabled.
+
+ Too many attempts. Face Unlock unavailable.
Too many attempts. Enter screen lock instead.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
index 7f5a67f8c3275334e4c22c600f45b0e3986e6141..57ffd240065dd2faee94c7daf3dc095645519784 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
@@ -19,6 +19,8 @@ package com.android.systemui.biometrics
import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator.Modality
import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.hardware.biometrics.BiometricConstants
+import android.hardware.face.FaceManager
import android.util.AttributeSet
import com.android.systemui.R
@@ -27,6 +29,7 @@ class AuthBiometricFingerprintAndFaceView(
context: Context,
attrs: AttributeSet?
) : AuthBiometricFingerprintView(context, attrs) {
+ var isFaceClass3 = false
constructor (context: Context) : this(context, null)
@@ -34,10 +37,22 @@ class AuthBiometricFingerprintAndFaceView(
override fun forceRequireConfirmation(@Modality modality: Int) = modality == TYPE_FACE
- override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int) = modality == TYPE_FACE
+ override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int, unsuccessfulReason: String) =
+ modality == TYPE_FACE && !(isFaceClass3 && isLockoutErrorString(unsuccessfulReason))
override fun onPointerDown(failedModalities: Set) = failedModalities.contains(TYPE_FACE)
override fun createIconController(): AuthIconController =
AuthBiometricFingerprintAndFaceIconController(mContext, mIconView, mIconViewOverlay)
+
+ private fun isLockoutErrorString(unsuccessfulReason: String) =
+ unsuccessfulReason == FaceManager.getErrorString(
+ mContext,
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
+ 0 /*vendorCode */
+ ) || unsuccessfulReason == FaceManager.getErrorString(
+ mContext,
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
+ 0 /*vendorCode */
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 13bb6d345dbd5dec28af2b1b924237fdd6cd3799..e04dd068006072d3225bd14ed90f3a07a50728b1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -258,7 +258,8 @@ public abstract class AuthBiometricView extends LinearLayout {
}
/** Ignore all events from this (secondary) modality except successful authentication. */
- protected boolean ignoreUnsuccessfulEventsFrom(@Modality int modality) {
+ protected boolean ignoreUnsuccessfulEventsFrom(@Modality int modality,
+ String unsuccessfulReason) {
return false;
}
@@ -541,7 +542,7 @@ public abstract class AuthBiometricView extends LinearLayout {
*/
public void onAuthenticationFailed(
@Modality int modality, @Nullable String failureReason) {
- if (ignoreUnsuccessfulEventsFrom(modality)) {
+ if (ignoreUnsuccessfulEventsFrom(modality, failureReason)) {
return;
}
@@ -556,7 +557,7 @@ public abstract class AuthBiometricView extends LinearLayout {
* @param error message
*/
public void onError(@Modality int modality, String error) {
- if (ignoreUnsuccessfulEventsFrom(modality)) {
+ if (ignoreUnsuccessfulEventsFrom(modality, error)) {
return;
}
@@ -586,7 +587,7 @@ public abstract class AuthBiometricView extends LinearLayout {
* @param help message
*/
public void onHelp(@Modality int modality, String help) {
- if (ignoreUnsuccessfulEventsFrom(modality)) {
+ if (ignoreUnsuccessfulEventsFrom(modality, help)) {
return;
}
if (mSize != AuthDialog.SIZE_MEDIUM) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 517f94fbe93892bce1b0f8292df6bb5d1b8b9a2c..f435b227542f74f655b2e16ceb93c08021197182 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
@@ -383,6 +384,8 @@ public class AuthContainerView extends LinearLayout
fingerprintAndFaceView.setSensorProperties(fpProperties);
fingerprintAndFaceView.setScaleFactorProvider(config.mScaleProvider);
fingerprintAndFaceView.updateOverrideIconLayoutParamsSize();
+ fingerprintAndFaceView.setFaceClass3(
+ faceProperties.sensorStrength == STRENGTH_STRONG);
mBiometricView = fingerprintAndFaceView;
} else if (fpProperties != null) {
final AuthBiometricFingerprintView fpView =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
index dd87f6e7f64b026fc29ddf08a646dd87c6d36157..f4dacabb18b1848f994765b8057a1c075e3fd423 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
@@ -18,6 +18,8 @@ package com.android.systemui.biometrics
import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
+import android.hardware.biometrics.BiometricConstants
+import android.hardware.face.FaceManager
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.View
@@ -34,6 +36,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.times
import org.mockito.junit.MockitoJUnit
@@ -98,7 +101,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
}
@Test
- fun ignoresFaceErrors() {
+ fun ignoresFaceErrors_faceIsNotClass3_notLockoutError() {
biometricView.onDialogAnimatedIn()
biometricView.onError(TYPE_FACE, "not a face")
waitForIdleSync()
@@ -113,5 +116,47 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
}
+ @Test
+ fun doNotIgnoresFaceErrors_faceIsClass3_notLockoutError() {
+ biometricView.isFaceClass3 = true
+ biometricView.onDialogAnimatedIn()
+ biometricView.onError(TYPE_FACE, "not a face")
+ waitForIdleSync()
+
+ assertThat(biometricView.isAuthenticating).isTrue()
+ verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_ERROR)
+
+ biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
+ TestableLooper.get(this).moveTimeForward(1000)
+ waitForIdleSync()
+
+ verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
+ }
+
+ @Test
+ fun doNotIgnoresFaceErrors_faceIsClass3_lockoutError() {
+ biometricView.isFaceClass3 = true
+ biometricView.onDialogAnimatedIn()
+ biometricView.onError(
+ TYPE_FACE,
+ FaceManager.getErrorString(
+ biometricView.context,
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
+ 0 /*vendorCode */
+ )
+ )
+ waitForIdleSync()
+
+ assertThat(biometricView.isAuthenticating).isTrue()
+ verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
+
+ biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
+ TestableLooper.get(this).moveTimeForward(1000)
+ waitForIdleSync()
+
+ verify(callback, times(2)).onAction(AuthBiometricView.Callback.ACTION_ERROR)
+ }
+
+
override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
}