From bf9a253a752c929ca2fe1f9740b74a8ba854b5dc Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Wed, 21 Jul 2021 15:30:28 -0700 Subject: [PATCH 1/2] 8/n: Add additional multi-sensor reject logic Send haptic feedback if face is rejected and UDFPS is not "active" Bug: 193089985 Test: atest CoexCoordinatorTest Test: manual Change-Id: I3b8d172b0f3cbf890344f6db3e1deff15f8737d3 --- .../biometrics/sensors/CoexCoordinator.java | 17 +++++++--- .../sensors/CoexCoordinatorTest.java | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) 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 f97cb8a67d811..f732a147e4b71 100644 --- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java @@ -268,11 +268,18 @@ public class CoexCoordinator { AuthenticationClient udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null); AuthenticationClient face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); if (isCurrentFaceAuth(client)) { - // UDFPS should still be running in this case, do not vibrate. However, we - // should notify the callback and finish the client, so that Keyguard and - // BiometricScheduler do not get stuck. - Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps); - callback.handleLifecycleAfterAuth(); + if (isUdfpsActivelyAuthing(udfps)) { + // UDFPS should still be running in this case, do not vibrate. However, we + // should notify the callback and finish the client, so that Keyguard and + // BiometricScheduler do not get stuck. + Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps); + callback.handleLifecycleAfterAuth(); + } else { + // UDFPS is not actively authenticating (finger not touching, already + // rejected, etc). + callback.sendHapticFeedback(); + callback.handleLifecycleAfterAuth(); + } } else if (isCurrentUdfps(client)) { // Face should either be running, or have already finished SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis); 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 fb05825a122b3..c6d1ac13df052 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 @@ -258,6 +258,40 @@ public class CoexCoordinatorTest { verify(mCallback).handleLifecycleAfterAuth(); } + @Test + public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() { + mCoexCoordinator.reset(); + + AuthenticationClient faceClient = mock(AuthenticationClient.class); + when(faceClient.isKeyguard()).thenReturn(true); + when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + + AuthenticationClient udfpsClient = mock(AuthenticationClient.class, + withSettings().extraInterfaces(Udfps.class)); + when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(udfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true); + + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); + + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient, + LockoutTracker.LOCKOUT_NONE, mCallback); + verify(mCallback, never()).sendHapticFeedback(); + verify(mCallback).handleLifecycleAfterAuth(); + + // BiometricScheduler removes the face authentication client after rejection + mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, faceClient); + + // Then UDFPS rejected + CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); + mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, udfpsClient, + LockoutTracker.LOCKOUT_NONE, udfpsCallback); + verify(udfpsCallback).sendHapticFeedback(); + verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); + verify(mCallback, never()).sendHapticFeedback(); + } + @Test public void testNonKeyguard_rejectAndNotLockedOut() { mCoexCoordinator.reset(); From bc33eb8b15db8448865eb1c401f041e7a0e94a16 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Wed, 21 Jul 2021 15:46:26 -0700 Subject: [PATCH 2/2] 9/n: Add test case for udfps rejection after face rejection This case is already supported by the logic. This CL simply adds a test case for it. Bug: 193089985 Test: atest CoexCoordinatorTest Change-Id: I02898d240f5c22033250b2d1a8476a743e2d4f45 --- .../sensors/CoexCoordinatorTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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 c6d1ac13df052..a169ebd0320f7 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 @@ -292,6 +292,40 @@ public class CoexCoordinatorTest { verify(mCallback, never()).sendHapticFeedback(); } + @Test + public void testKeyguard_udfpsRejected_thenFaceRejected() { + mCoexCoordinator.reset(); + + AuthenticationClient faceClient = mock(AuthenticationClient.class); + when(faceClient.isKeyguard()).thenReturn(true); + when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + + AuthenticationClient udfpsClient = mock(AuthenticationClient.class, + withSettings().extraInterfaces(Udfps.class)); + when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(udfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true); + + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); + + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient, + LockoutTracker.LOCKOUT_NONE, mCallback); + // Client becomes paused, but finger does not necessarily lift, since we suppress the haptic + when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED_PAUSED); + verify(mCallback, never()).sendHapticFeedback(); + verify(mCallback).handleLifecycleAfterAuth(); + + // Then face rejected. Note that scheduler leaves UDFPS in the CoexCoordinator since + // unlike face, its lifecycle becomes "paused" instead of "finished". + CoexCoordinator.Callback faceCallback = mock(CoexCoordinator.Callback.class); + mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, faceClient, + LockoutTracker.LOCKOUT_NONE, faceCallback); + verify(faceCallback).sendHapticFeedback(); + verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); + verify(mCallback, never()).sendHapticFeedback(); + } + @Test public void testNonKeyguard_rejectAndNotLockedOut() { mCoexCoordinator.reset();