summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java68
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java26
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java23
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java81
8 files changed, 192 insertions, 44 deletions
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 705b3aa12391..6f38ed04cd96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -82,7 +82,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
private long mStartTimeMs;
- protected boolean mAuthAttempted;
+ private boolean mAuthAttempted;
// TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
// the state. We should think of a way to improve this in the future.
@@ -98,6 +98,12 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
*/
protected abstract void handleLifecycleAfterAuth(boolean authenticated);
+ /**
+ * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
+ * etc)
+ */
+ public abstract boolean wasUserDetected();
+
public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
@@ -381,9 +387,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
}
@Override
- public void onError(int errorCode, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int errorCode, int vendorCode) {
super.onError(errorCode, vendorCode);
mState = STATE_STOPPED;
+
+ CoexCoordinator.getInstance().onAuthenticationError(this, errorCode, this::vibrateError);
}
/**
@@ -445,4 +453,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
public boolean interruptsPrecedingClients() {
return true;
}
+
+ public boolean wasAuthAttempted() {
+ return mAuthAttempted;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index 6d32fdebe582..25d4a38cd475 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -22,6 +22,7 @@ import static com.android.server.biometrics.sensors.BiometricScheduler.sensorTyp
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
import android.os.Looper;
import android.util.Slog;
@@ -54,7 +55,7 @@ public class CoexCoordinator {
/**
* Callback interface notifying the owner of "results" from the CoexCoordinator's business
- * logic.
+ * logic for accept and reject.
*/
interface Callback {
/**
@@ -80,6 +81,17 @@ public class CoexCoordinator {
void sendAuthenticationCanceled();
}
+ /**
+ * Callback interface notifying the owner of "results" from the CoexCoordinator's business
+ * logic for errors.
+ */
+ interface ErrorCallback {
+ /**
+ * Requests the owner to initiate a vibration for this event.
+ */
+ void sendHapticFeedback();
+ }
+
private static CoexCoordinator sInstance;
@VisibleForTesting
@@ -203,6 +215,9 @@ public class CoexCoordinator {
mClientMap.remove(sensorType);
}
+ /**
+ * Notify the coordinator that authentication succeeded (accepted)
+ */
public void onAuthenticationSucceeded(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@NonNull Callback callback) {
@@ -273,6 +288,9 @@ public class CoexCoordinator {
}
}
+ /**
+ * Notify the coordinator that a rejection has occurred.
+ */
public void onAuthenticationRejected(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@LockoutTracker.LockoutMode int lockoutMode,
@@ -357,6 +375,54 @@ public class CoexCoordinator {
}
}
+ /**
+ * Notify the coordinator that an error has occurred.
+ */
+ public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
+ @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
+ // Figure out non-coex state
+ final boolean shouldUsuallyVibrate;
+ if (isCurrentFaceAuth(client)) {
+ final boolean notDetectedOnKeyguard = client.isKeyguard() && !client.wasUserDetected();
+ final boolean authAttempted = client.wasAuthAttempted();
+
+ switch (error) {
+ case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
+ case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
+ case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
+ shouldUsuallyVibrate = authAttempted && !notDetectedOnKeyguard;
+ break;
+ default:
+ shouldUsuallyVibrate = false;
+ break;
+ }
+ } else {
+ shouldUsuallyVibrate = false;
+ }
+
+ // Figure out coex state
+ final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
+ final boolean hapticSuppressedByCoex;
+
+ if (keyguardAdvancedLogic) {
+ if (isSingleAuthOnly(client)) {
+ hapticSuppressedByCoex = false;
+ } else {
+ hapticSuppressedByCoex = isCurrentFaceAuth(client)
+ && !client.isKeyguardBypassEnabled();
+ }
+ } else {
+ hapticSuppressedByCoex = false;
+ }
+
+ // Combine and send feedback if appropriate
+ Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate
+ + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
+ if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
+ callback.sendHapticFeedback();
+ }
+ }
+
@Nullable
private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) {
for (SuccessfulAuth auth : mSuccessfulAuths) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index f7fd8d0972f6..d66a27920f49 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -127,7 +127,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
}
}
- private boolean wasUserDetected() {
+ @Override
+ public boolean wasUserDetected() {
// Do not provide haptic feedback if the user was not detected, and an error (usually
// ERROR_TIMEOUT) is received.
return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -160,7 +161,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
}
@Override
- public void onError(int error, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int error, int vendorCode) {
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
getStartTimeMs(),
System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -169,25 +170,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
vendorCode,
getTargetUserId()));
- switch (error) {
- case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
- if (!wasUserDetected() && !isBiometricPrompt()) {
- // No vibration if user was not detected on keyguard
- break;
- }
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
- if (mAuthAttempted) {
- // Only vibrate if auth was attempted. If the user was already locked out prior
- // to starting authentication, do not vibrate.
- vibrateError();
- }
- break;
- case BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL:
- BiometricNotificationUtils.showReEnrollmentNotification(getContext());
- break;
- default:
- break;
+ if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
+ BiometricNotificationUtils.showReEnrollmentNotification(getContext());
}
super.onError(error, vendorCode);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index c33b957223a4..33950af2216f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -115,7 +115,8 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
}
}
- private boolean wasUserDetected() {
+ @Override
+ public boolean wasUserDetected() {
// Do not provide haptic feedback if the user was not detected, and an error (usually
// ERROR_TIMEOUT) is received.
return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -147,7 +148,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
}
@Override
- public void onError(int error, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int error, int vendorCode) {
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
getStartTimeMs(),
System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -156,24 +157,6 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
vendorCode,
getTargetUserId()));
- switch (error) {
- case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
- if (!wasUserDetected() && !isBiometricPrompt()) {
- // No vibration if user was not detected on keyguard
- break;
- }
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
- if (mAuthAttempted) {
- // Only vibrate if auth was attempted. If the user was already locked out prior
- // to starting authentication, do not vibrate.
- vibrateError();
- }
- break;
- default:
- break;
- }
-
super.onError(error, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 8835c1e02610..37ee76adeece 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -106,6 +106,12 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
}
@Override
+ public boolean wasUserDetected() {
+ // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+ return false;
+ }
+
+ @Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> token) {
super.onAuthenticated(identifier, authenticated, token);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 83f1480f4611..5060744bb33e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -153,6 +153,12 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
}
@Override
+ public boolean wasUserDetected() {
+ // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+ return false;
+ }
+
+ @Override
public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
return super.handleFailedAttempt(userId);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 8592166aae15..f4d14995f7c7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -378,6 +378,11 @@ public class BiometricSchedulerTest {
protected void handleLifecycleAfterAuth(boolean authenticated) {
}
+
+ @Override
+ public boolean wasUserDetected() {
+ return false;
+ }
}
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
@@ -407,6 +412,11 @@ public class BiometricSchedulerTest {
protected void handleLifecycleAfterAuth(boolean authenticated) {
}
+
+ @Override
+ public boolean wasUserDetected() {
+ return false;
+ }
}
private static class TestClientMonitor2 extends TestClientMonitor {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index 1263f7b84db1..bfb0be760f85 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
@@ -61,6 +62,8 @@ public class CoexCoordinatorTest {
private Context mContext;
@Mock
private CoexCoordinator.Callback mCallback;
+ @Mock
+ private CoexCoordinator.ErrorCallback mErrorCallback;
@Before
public void setUp() {
@@ -490,4 +493,82 @@ public class CoexCoordinatorTest {
verify(callback).handleLifecycleAfterAuth();
verify(successfulAuths).remove(eq(auth));
}
+
+ @Test
+ public void testBiometricPrompt_FaceError() {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ when(client.isBiometricPrompt()).thenReturn(true);
+ when(client.wasAuthAttempted()).thenReturn(true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+ mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ mErrorCallback);
+ verify(mErrorCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testKeyguard_faceAuthOnly_errorWhenBypassEnabled() {
+ testKeyguard_faceAuthOnly(true /* bypassEnabled */);
+ }
+
+ @Test
+ public void testKeyguard_faceAuthOnly_errorWhenBypassDisabled() {
+ testKeyguard_faceAuthOnly(false /* bypassEnabled */);
+ }
+
+ private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ when(client.isKeyguard()).thenReturn(true);
+ when(client.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(client.wasAuthAttempted()).thenReturn(true);
+ when(client.wasUserDetected()).thenReturn(true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+ mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ mErrorCallback);
+ verify(mErrorCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testKeyguard_coex_faceErrorWhenBypassEnabled() {
+ testKeyguard_coex_faceError(true /* bypassEnabled */);
+ }
+
+ @Test
+ public void testKeyguard_coex_faceErrorWhenBypassDisabled() {
+ testKeyguard_coex_faceError(false /* bypassEnabled */);
+ }
+
+ private void testKeyguard_coex_faceError(boolean bypassEnabled) {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+ when(faceClient.isKeyguard()).thenReturn(true);
+ when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(faceClient.wasAuthAttempted()).thenReturn(true);
+ when(faceClient.wasUserDetected()).thenReturn(true);
+
+ AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
+ withSettings().extraInterfaces(Udfps.class));
+ when(udfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+
+ mCoexCoordinator.onAuthenticationError(faceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+
+ if (bypassEnabled) {
+ verify(mErrorCallback).sendHapticFeedback();
+ } else {
+ verify(mErrorCallback, never()).sendHapticFeedback();
+ }
+ }
}