Loading services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +27 −1 Original line number Original line Diff line number Diff line Loading @@ -180,7 +180,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> + ", isBP: " + isBiometricPrompt() + ", isBP: " + isBiometricPrompt() + ", listener: " + listener + ", listener: " + listener + ", requireConfirmation: " + mRequireConfirmation + ", requireConfirmation: " + mRequireConfirmation + ", user: " + getTargetUserId()); + ", user: " + getTargetUserId() + ", clientMonitor: " + toString()); final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId()); final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId()); if (isCryptoOperation()) { if (isCryptoOperation()) { Loading Loading @@ -304,6 +305,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public void handleLifecycleAfterAuth() { public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */); AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */); } } @Override public void sendAuthenticationCanceled() { sendCancelOnly(listener); } }); }); } else { } else { // Allow system-defined limit of number of attempts before giving up // Allow system-defined limit of number of attempts before giving up Loading Loading @@ -338,10 +344,30 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public void handleLifecycleAfterAuth() { public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */); AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */); } } @Override public void sendAuthenticationCanceled() { sendCancelOnly(listener); } }); }); } } } } private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) { if (listener == null) { Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null"); return; } try { listener.onError(getSensorId(), getCookie(), BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } } @Override @Override public void onAcquired(int acquiredInfo, int vendorCode) { public void onAcquired(int acquiredInfo, int vendorCode) { super.onAcquired(acquiredInfo, vendorCode); super.onAcquired(acquiredInfo, vendorCode); Loading services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +15 −2 Original line number Original line Diff line number Diff line Loading @@ -73,6 +73,11 @@ public class CoexCoordinator { * from scheduler if auth was successful). * from scheduler if auth was successful). */ */ void handleLifecycleAfterAuth(); void handleLifecycleAfterAuth(); /** * Requests the owner to notify the caller that authentication was canceled. */ void sendAuthenticationCanceled(); } } private static CoexCoordinator sInstance; private static CoexCoordinator sInstance; Loading Loading @@ -356,7 +361,11 @@ public class CoexCoordinator { private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) { private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) { for (SuccessfulAuth auth : mSuccessfulAuths) { for (SuccessfulAuth auth : mSuccessfulAuths) { if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) { if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) { Slog.d(TAG, "Removing stale auth: " + auth); // TODO(b/193089985): This removes the auth but does not notify the client with // an appropriate lifecycle event (such as ERROR_CANCELED), and violates the // API contract. However, this might be OK for now since the validity duration // is way longer than the time it takes to auth with fingerprint. Slog.e(TAG, "Removing stale auth: " + auth); mSuccessfulAuths.remove(auth); mSuccessfulAuths.remove(auth); } else if (auth.mSensorType == SENSOR_TYPE_FACE) { } else if (auth.mSensorType == SENSOR_TYPE_FACE) { mSuccessfulAuths.remove(auth); mSuccessfulAuths.remove(auth); Loading @@ -367,9 +376,13 @@ public class CoexCoordinator { } } private void removeAndFinishAllFaceFromQueue() { private void removeAndFinishAllFaceFromQueue() { // Note that these auth are all successful, but have never notified the client (e.g. // keyguard). To comply with the authentication lifecycle, we must notify the client that // auth is "done". The safest thing to do is to send ERROR_CANCELED. for (SuccessfulAuth auth : mSuccessfulAuths) { for (SuccessfulAuth auth : mSuccessfulAuths) { if (auth.mSensorType == SENSOR_TYPE_FACE) { if (auth.mSensorType == SENSOR_TYPE_FACE) { Slog.d(TAG, "Removing from queue and finishing: " + auth); Slog.d(TAG, "Removing from queue, canceling, and finishing: " + auth); auth.mCallback.sendAuthenticationCanceled(); auth.mCallback.handleLifecycleAfterAuth(); auth.mCallback.handleLifecycleAfterAuth(); mSuccessfulAuths.remove(auth); mSuccessfulAuths.remove(auth); } } Loading services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java +14 −9 Original line number Original line Diff line number Diff line Loading @@ -255,13 +255,16 @@ public class CoexCoordinatorTest { mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); // For easier reading final CoexCoordinator.Callback faceCallback = mCallback; mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, mCallback); faceCallback); verify(mCallback, never()).sendHapticFeedback(); verify(faceCallback, never()).sendHapticFeedback(); verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); verify(faceCallback, never()).sendAuthenticationResult(anyBoolean()); // CoexCoordinator requests the system to hold onto this AuthenticationClient until // CoexCoordinator requests the system to hold onto this AuthenticationClient until // UDFPS result is known // UDFPS result is known verify(mCallback, never()).handleLifecycleAfterAuth(); verify(faceCallback, never()).handleLifecycleAfterAuth(); // Reset the mock // Reset the mock CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); Loading @@ -274,6 +277,8 @@ public class CoexCoordinatorTest { verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */); verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */); verify(udfpsCallback).handleLifecycleAfterAuth(); verify(udfpsCallback).handleLifecycleAfterAuth(); verify(faceCallback).sendAuthenticationCanceled(); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); } else { } else { mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient, mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient, Loading @@ -281,16 +286,16 @@ public class CoexCoordinatorTest { if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) { if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) { verify(udfpsCallback, never()).sendHapticFeedback(); verify(udfpsCallback, never()).sendHapticFeedback(); verify(mCallback).sendHapticFeedback(); verify(faceCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); verify(faceCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); verify(mCallback).handleLifecycleAfterAuth(); verify(faceCallback).handleLifecycleAfterAuth(); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); } else { } else { assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); verify(mCallback, never()).sendHapticFeedback(); verify(faceCallback, never()).sendHapticFeedback(); verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); verify(faceCallback, never()).sendAuthenticationResult(anyBoolean()); verify(udfpsCallback).sendHapticFeedback(); verify(udfpsCallback).sendHapticFeedback(); verify(udfpsCallback) verify(udfpsCallback) Loading Loading
services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +27 −1 Original line number Original line Diff line number Diff line Loading @@ -180,7 +180,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> + ", isBP: " + isBiometricPrompt() + ", isBP: " + isBiometricPrompt() + ", listener: " + listener + ", listener: " + listener + ", requireConfirmation: " + mRequireConfirmation + ", requireConfirmation: " + mRequireConfirmation + ", user: " + getTargetUserId()); + ", user: " + getTargetUserId() + ", clientMonitor: " + toString()); final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId()); final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId()); if (isCryptoOperation()) { if (isCryptoOperation()) { Loading Loading @@ -304,6 +305,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public void handleLifecycleAfterAuth() { public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */); AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */); } } @Override public void sendAuthenticationCanceled() { sendCancelOnly(listener); } }); }); } else { } else { // Allow system-defined limit of number of attempts before giving up // Allow system-defined limit of number of attempts before giving up Loading Loading @@ -338,10 +344,30 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public void handleLifecycleAfterAuth() { public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */); AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */); } } @Override public void sendAuthenticationCanceled() { sendCancelOnly(listener); } }); }); } } } } private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) { if (listener == null) { Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null"); return; } try { listener.onError(getSensorId(), getCookie(), BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } } @Override @Override public void onAcquired(int acquiredInfo, int vendorCode) { public void onAcquired(int acquiredInfo, int vendorCode) { super.onAcquired(acquiredInfo, vendorCode); super.onAcquired(acquiredInfo, vendorCode); Loading
services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +15 −2 Original line number Original line Diff line number Diff line Loading @@ -73,6 +73,11 @@ public class CoexCoordinator { * from scheduler if auth was successful). * from scheduler if auth was successful). */ */ void handleLifecycleAfterAuth(); void handleLifecycleAfterAuth(); /** * Requests the owner to notify the caller that authentication was canceled. */ void sendAuthenticationCanceled(); } } private static CoexCoordinator sInstance; private static CoexCoordinator sInstance; Loading Loading @@ -356,7 +361,11 @@ public class CoexCoordinator { private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) { private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) { for (SuccessfulAuth auth : mSuccessfulAuths) { for (SuccessfulAuth auth : mSuccessfulAuths) { if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) { if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) { Slog.d(TAG, "Removing stale auth: " + auth); // TODO(b/193089985): This removes the auth but does not notify the client with // an appropriate lifecycle event (such as ERROR_CANCELED), and violates the // API contract. However, this might be OK for now since the validity duration // is way longer than the time it takes to auth with fingerprint. Slog.e(TAG, "Removing stale auth: " + auth); mSuccessfulAuths.remove(auth); mSuccessfulAuths.remove(auth); } else if (auth.mSensorType == SENSOR_TYPE_FACE) { } else if (auth.mSensorType == SENSOR_TYPE_FACE) { mSuccessfulAuths.remove(auth); mSuccessfulAuths.remove(auth); Loading @@ -367,9 +376,13 @@ public class CoexCoordinator { } } private void removeAndFinishAllFaceFromQueue() { private void removeAndFinishAllFaceFromQueue() { // Note that these auth are all successful, but have never notified the client (e.g. // keyguard). To comply with the authentication lifecycle, we must notify the client that // auth is "done". The safest thing to do is to send ERROR_CANCELED. for (SuccessfulAuth auth : mSuccessfulAuths) { for (SuccessfulAuth auth : mSuccessfulAuths) { if (auth.mSensorType == SENSOR_TYPE_FACE) { if (auth.mSensorType == SENSOR_TYPE_FACE) { Slog.d(TAG, "Removing from queue and finishing: " + auth); Slog.d(TAG, "Removing from queue, canceling, and finishing: " + auth); auth.mCallback.sendAuthenticationCanceled(); auth.mCallback.handleLifecycleAfterAuth(); auth.mCallback.handleLifecycleAfterAuth(); mSuccessfulAuths.remove(auth); mSuccessfulAuths.remove(auth); } } Loading
services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java +14 −9 Original line number Original line Diff line number Diff line Loading @@ -255,13 +255,16 @@ public class CoexCoordinatorTest { mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); // For easier reading final CoexCoordinator.Callback faceCallback = mCallback; mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, mCallback); faceCallback); verify(mCallback, never()).sendHapticFeedback(); verify(faceCallback, never()).sendHapticFeedback(); verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); verify(faceCallback, never()).sendAuthenticationResult(anyBoolean()); // CoexCoordinator requests the system to hold onto this AuthenticationClient until // CoexCoordinator requests the system to hold onto this AuthenticationClient until // UDFPS result is known // UDFPS result is known verify(mCallback, never()).handleLifecycleAfterAuth(); verify(faceCallback, never()).handleLifecycleAfterAuth(); // Reset the mock // Reset the mock CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); Loading @@ -274,6 +277,8 @@ public class CoexCoordinatorTest { verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */); verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */); verify(udfpsCallback).handleLifecycleAfterAuth(); verify(udfpsCallback).handleLifecycleAfterAuth(); verify(faceCallback).sendAuthenticationCanceled(); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); } else { } else { mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient, mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient, Loading @@ -281,16 +286,16 @@ public class CoexCoordinatorTest { if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) { if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) { verify(udfpsCallback, never()).sendHapticFeedback(); verify(udfpsCallback, never()).sendHapticFeedback(); verify(mCallback).sendHapticFeedback(); verify(faceCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); verify(faceCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); verify(mCallback).handleLifecycleAfterAuth(); verify(faceCallback).handleLifecycleAfterAuth(); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); } else { } else { assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); verify(mCallback, never()).sendHapticFeedback(); verify(faceCallback, never()).sendHapticFeedback(); verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); verify(faceCallback, never()).sendAuthenticationResult(anyBoolean()); verify(udfpsCallback).sendHapticFeedback(); verify(udfpsCallback).sendHapticFeedback(); verify(udfpsCallback) verify(udfpsCallback) Loading