Loading packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +4 −0 Original line number Diff line number Diff line Loading @@ -3380,6 +3380,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut); pw.println(" mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); if (isUdfpsEnrolled()) { pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); Loading @@ -3401,8 +3402,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab + getStrongAuthTracker().hasUserAuthenticatedSinceBoot()); pw.println(" disabled(DPM)=" + isFaceDisabled(userId)); pw.println(" possible=" + isUnlockWithFacePossible(userId)); pw.println(" listening: actual=" + mFaceRunningState + " expected=(" + (shouldListenForFace() ? 1 : 0)); pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); pw.println(" mFaceLockedOutPermanent=" + mFaceLockedOutPermanent); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched); } Loading services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +51 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.biometrics.sensors; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; Loading @@ -30,6 +31,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.security.KeyStore; import android.util.EventLog; import android.util.Slog; Loading @@ -48,6 +50,18 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> private static final String TAG = "Biometrics/AuthenticationClient"; // New, has not started yet public static final int STATE_NEW = 0; // Framework/HAL have started this operation public static final int STATE_STARTED = 1; // Operation is started, but requires some user action (such as finger lift & re-touch) public static final int STATE_STARTED_PAUSED = 2; // Done, errored, canceled, etc. HAL/framework are not running this sensor anymore. public static final int STATE_STOPPED = 3; @IntDef({STATE_NEW, STATE_STARTED, STATE_STARTED_PAUSED, STATE_STOPPED}) @interface State {} private final boolean mIsStrongBiometric; private final boolean mRequireConfirmation; private final ActivityTaskManager mActivityTaskManager; Loading @@ -63,6 +77,20 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> protected 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. protected @State int mState = STATE_NEW; /** * Handles lifecycle, e.g. {@link BiometricScheduler}, * {@link com.android.server.biometrics.sensors.BaseClientMonitor.Callback} after authentication * results are known. Note that this happens asynchronously from (but shortly after) * {@link #onAuthenticated(BiometricAuthenticator.Identifier, boolean, ArrayList)} and allows * {@link CoexCoordinator} a chance to invoke/delay this event. * @param authenticated */ protected abstract void handleLifecycleAfterAuth(boolean authenticated); public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, @NonNull String owner, Loading Loading @@ -221,7 +249,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } final CoexCoordinator coordinator = CoexCoordinator.getInstance(); coordinator.onAuthenticationSucceeded(this, new CoexCoordinator.Callback() { coordinator.onAuthenticationSucceeded(SystemClock.uptimeMillis(), this, new CoexCoordinator.Callback() { @Override public void sendAuthenticationResult(boolean addAuthTokenIfStrong) { if (addAuthTokenIfStrong && mIsStrongBiometric) { Loading Loading @@ -262,6 +291,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> vibrateSuccess(); } } @Override public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */); } }); } else { // Allow system-defined limit of number of attempts before giving up Loading @@ -272,7 +306,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } final CoexCoordinator coordinator = CoexCoordinator.getInstance(); coordinator.onAuthenticationRejected(this, lockoutMode, coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode, new CoexCoordinator.Callback() { @Override public void sendAuthenticationResult(boolean addAuthTokenIfStrong) { Loading @@ -291,6 +325,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> vibrateError(); } } @Override public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */); } }); } } Loading @@ -307,6 +346,12 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } } @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); mState = STATE_STOPPED; } /** * Start authentication */ Loading Loading @@ -345,6 +390,10 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } } public @State int getState() { return mState; } @Override public int getProtoEnum() { return BiometricsProto.CM_AUTHENTICATE; Loading services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +0 −1 Original line number Diff line number Diff line Loading @@ -355,7 +355,6 @@ public class BiometricScheduler { /** * Creates a new scheduler. * @param context system_server context. * @param tag for the specific instance of the scheduler. Should be unique. * @param sensorType the sensorType that this scheduler is handling. * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures Loading services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +182 −10 Original line number Diff line number Diff line Loading @@ -22,12 +22,17 @@ import static com.android.server.biometrics.sensors.BiometricScheduler.sensorTyp import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.sensors.BiometricScheduler.SensorType; import com.android.server.biometrics.sensors.fingerprint.Udfps; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; /** Loading @@ -43,6 +48,9 @@ public class CoexCoordinator { "com.android.server.biometrics.sensors.CoexCoordinator.enable"; private static final boolean DEBUG = true; // Successful authentications should be used within this amount of time. static final long SUCCESSFUL_AUTH_VALID_DURATION_MS = 5000; /** * Callback interface notifying the owner of "results" from the CoexCoordinator's business * logic. Loading @@ -58,10 +66,69 @@ public class CoexCoordinator { * Requests the owner to initiate a vibration for this event. */ void sendHapticFeedback(); /** * Requests the owner to handle the AuthenticationClient's lifecycle (e.g. finish and remove * from scheduler if auth was successful). */ void handleLifecycleAfterAuth(); } private static CoexCoordinator sInstance; @VisibleForTesting public static class SuccessfulAuth { final long mAuthTimestamp; final @SensorType int mSensorType; final AuthenticationClient<?> mAuthenticationClient; final Callback mCallback; final CleanupRunnable mCleanupRunnable; public static class CleanupRunnable implements Runnable { @NonNull final LinkedList<SuccessfulAuth> mSuccessfulAuths; @NonNull final SuccessfulAuth mAuth; @NonNull final Callback mCallback; public CleanupRunnable(@NonNull LinkedList<SuccessfulAuth> successfulAuths, @NonNull SuccessfulAuth auth, @NonNull Callback callback) { mSuccessfulAuths = successfulAuths; mAuth = auth; mCallback = callback; } @Override public void run() { final boolean removed = mSuccessfulAuths.remove(mAuth); Slog.w(TAG, "Removing stale successfulAuth: " + mAuth.toString() + ", success: " + removed); mCallback.handleLifecycleAfterAuth(); } } public SuccessfulAuth(@NonNull Handler handler, @NonNull LinkedList<SuccessfulAuth> successfulAuths, long currentTimeMillis, @SensorType int sensorType, @NonNull AuthenticationClient<?> authenticationClient, @NonNull Callback callback) { mAuthTimestamp = currentTimeMillis; mSensorType = sensorType; mAuthenticationClient = authenticationClient; mCallback = callback; mCleanupRunnable = new CleanupRunnable(successfulAuths, this, callback); handler.postDelayed(mCleanupRunnable, SUCCESSFUL_AUTH_VALID_DURATION_MS); } @Override public String toString() { return "SensorType: " + sensorTypeToString(mSensorType) + ", mAuthTimestamp: " + mAuthTimestamp + ", authenticationClient: " + mAuthenticationClient; } } /** * @return a singleton instance. */ Loading @@ -85,11 +152,15 @@ public class CoexCoordinator { // SensorType to AuthenticationClient map private final Map<Integer, AuthenticationClient<?>> mClientMap; @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths; private boolean mAdvancedLogicEnabled; private final Handler mHandler; private CoexCoordinator() { // Singleton mClientMap = new HashMap<>(); mSuccessfulAuths = new LinkedList<>(); mHandler = new Handler(Looper.getMainLooper()); } public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType, Loading Loading @@ -121,34 +192,43 @@ public class CoexCoordinator { mClientMap.remove(sensorType); } public void onAuthenticationSucceeded(@NonNull AuthenticationClient<?> client, public void onAuthenticationSucceeded(long currentTimeMillis, @NonNull AuthenticationClient<?> client, @NonNull Callback callback) { if (client.isBiometricPrompt()) { callback.sendHapticFeedback(); // For BP, BiometricService will add the authToken to Keystore. callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } else if (isUnknownClient(client)) { // Client doesn't exist in our map for some reason. Give the user feedback so the // device doesn't feel like it's stuck. All other cases below can assume that the // client exists in our map. callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } else if (mAdvancedLogicEnabled && client.isKeyguard()) { if (isSingleAuthOnly(client)) { // Single sensor authentication callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } else { // Multi sensor authentication AuthenticationClient<?> udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null); AuthenticationClient<?> face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); if (isCurrentFaceAuth(client)) { if (isPointerDown(udfps)) { // Face auth success while UDFPS pointer down. No callback, no haptic. // Feedback will be provided after UDFPS result. if (isUdfpsActivelyAuthing(udfps)) { // Face auth success while UDFPS is actively authing. No callback, no haptic // Feedback will be provided after UDFPS result: // 1) UDFPS succeeds - simply remove this from the queue // 2) UDFPS rejected - use this face auth success to notify clients mSuccessfulAuths.add(new SuccessfulAuth(mHandler, mSuccessfulAuths, currentTimeMillis, SENSOR_TYPE_FACE, client, callback)); } else { callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } } else if (isCurrentUdfps(client)) { if (isFaceScanning()) { Loading @@ -156,8 +236,12 @@ public class CoexCoordinator { // Cancel face auth and/or prevent it from invoking haptics/callbacks after face.cancel(); } removeAndFinishAllFaceFromQueue(); callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } } } else { Loading @@ -165,13 +249,68 @@ public class CoexCoordinator { // FingerprintManager for highlighting fingers callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } } public void onAuthenticationRejected(@NonNull AuthenticationClient<?> client, public void onAuthenticationRejected(long currentTimeMillis, @NonNull AuthenticationClient<?> client, @LockoutTracker.LockoutMode int lockoutMode, @NonNull Callback callback) { final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard(); if (keyguardAdvancedLogic) { if (isSingleAuthOnly(client)) { callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } else { // Multi sensor authentication 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(); } else if (isCurrentUdfps(client)) { // Face should either be running, or have already finished SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis); if (auth != null) { Slog.d(TAG, "Using recent auth: " + auth); callback.handleLifecycleAfterAuth(); auth.mCallback.sendHapticFeedback(); auth.mCallback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); auth.mCallback.handleLifecycleAfterAuth(); } else if (isFaceScanning()) { // UDFPS rejected but face is still scanning Slog.d(TAG, "UDFPS rejected in multi-sensor auth, face: " + face); callback.handleLifecycleAfterAuth(); // TODO(b/193089985): Enforce/ensure that face auth finishes (whether // accept/reject) within X amount of time. Otherwise users will be stuck // waiting with their finger down for a long time. } else { // Face not scanning, and was not found in the queue. Most likely, face // auth was too long ago. Slog.d(TAG, "UDFPS rejected in multi-sensor auth, face not scanning"); callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } } else { Slog.d(TAG, "Unknown client rejected: " + client); callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } } } else { callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } // Always notify keyguard, otherwise the cached "running" state in KeyguardUpdateMonitor // will get stuck. if (lockoutMode == LockoutTracker.LOCKOUT_NONE) { // Don't send onAuthenticationFailed if we're in lockout, it causes a // janky UI on Keyguard/BiometricPrompt since "authentication failed" Loading @@ -180,6 +319,30 @@ public class CoexCoordinator { } } @Nullable private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) { for (SuccessfulAuth auth : mSuccessfulAuths) { if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) { Slog.d(TAG, "Removing stale auth: " + auth); mSuccessfulAuths.remove(auth); } else if (auth.mSensorType == SENSOR_TYPE_FACE) { mSuccessfulAuths.remove(auth); return auth; } } return null; } private void removeAndFinishAllFaceFromQueue() { for (SuccessfulAuth auth : mSuccessfulAuths) { if (auth.mSensorType == SENSOR_TYPE_FACE) { Slog.d(TAG, "Removing from queue and finishing: " + auth); auth.mCallback.handleLifecycleAfterAuth(); mSuccessfulAuths.remove(auth); } } } private boolean isCurrentFaceAuth(@NonNull AuthenticationClient<?> client) { return client == mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); } Loading @@ -189,12 +352,13 @@ public class CoexCoordinator { } private boolean isFaceScanning() { return mClientMap.containsKey(SENSOR_TYPE_FACE); AuthenticationClient<?> client = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); return client != null && client.getState() == AuthenticationClient.STATE_STARTED; } private static boolean isPointerDown(@Nullable AuthenticationClient<?> client) { private static boolean isUdfpsActivelyAuthing(@Nullable AuthenticationClient<?> client) { if (client instanceof Udfps) { return ((Udfps) client).isPointerDown(); return client.getState() == AuthenticationClient.STATE_STARTED; } return false; } Loading @@ -221,7 +385,15 @@ public class CoexCoordinator { return true; } @Override public String toString() { return "Enabled: " + mAdvancedLogicEnabled; StringBuilder sb = new StringBuilder(); sb.append("Enabled: ").append(mAdvancedLogicEnabled); sb.append(", Queue size: " ).append(mSuccessfulAuths.size()); for (SuccessfulAuth auth : mSuccessfulAuths) { sb.append(", Auth: ").append(auth.toString()); } return sb.toString(); } } services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +16 −6 Original line number Diff line number Diff line Loading @@ -89,6 +89,12 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements R.array.config_face_acquire_vendor_keyguard_ignorelist); } @Override public void start(@NonNull Callback callback) { super.start(callback); mState = STATE_STARTED; } @NonNull @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { Loading Loading @@ -127,11 +133,21 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements && mLastAcquire != FaceManager.FACE_ACQUIRED_UNKNOWN; } @Override protected void handleLifecycleAfterAuth(boolean authenticated) { // For face, the authentication lifecycle ends either when // 1) Authenticated == true // 2) Error occurred // 3) Authenticated == false mCallback.onClientFinished(this, true /* success */); } @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> token) { super.onAuthenticated(identifier, authenticated, token); mState = STATE_STOPPED; mUsageStats.addEvent(new UsageStats.AuthenticationEvent( getStartTimeMs(), System.currentTimeMillis() - getStartTimeMs() /* latency */, Loading @@ -139,12 +155,6 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements 0 /* error */, 0 /* vendorError */, getTargetUserId())); // For face, the authentication lifecycle ends either when // 1) Authenticated == true // 2) Error occurred // 3) Authenticated == false mCallback.onClientFinished(this, true /* success */); } @Override Loading Loading
packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +4 −0 Original line number Diff line number Diff line Loading @@ -3380,6 +3380,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut); pw.println(" mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); if (isUdfpsEnrolled()) { pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); Loading @@ -3401,8 +3402,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab + getStrongAuthTracker().hasUserAuthenticatedSinceBoot()); pw.println(" disabled(DPM)=" + isFaceDisabled(userId)); pw.println(" possible=" + isUnlockWithFacePossible(userId)); pw.println(" listening: actual=" + mFaceRunningState + " expected=(" + (shouldListenForFace() ? 1 : 0)); pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); pw.println(" mFaceLockedOutPermanent=" + mFaceLockedOutPermanent); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched); } Loading
services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +51 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.biometrics.sensors; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; Loading @@ -30,6 +31,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.security.KeyStore; import android.util.EventLog; import android.util.Slog; Loading @@ -48,6 +50,18 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> private static final String TAG = "Biometrics/AuthenticationClient"; // New, has not started yet public static final int STATE_NEW = 0; // Framework/HAL have started this operation public static final int STATE_STARTED = 1; // Operation is started, but requires some user action (such as finger lift & re-touch) public static final int STATE_STARTED_PAUSED = 2; // Done, errored, canceled, etc. HAL/framework are not running this sensor anymore. public static final int STATE_STOPPED = 3; @IntDef({STATE_NEW, STATE_STARTED, STATE_STARTED_PAUSED, STATE_STOPPED}) @interface State {} private final boolean mIsStrongBiometric; private final boolean mRequireConfirmation; private final ActivityTaskManager mActivityTaskManager; Loading @@ -63,6 +77,20 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> protected 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. protected @State int mState = STATE_NEW; /** * Handles lifecycle, e.g. {@link BiometricScheduler}, * {@link com.android.server.biometrics.sensors.BaseClientMonitor.Callback} after authentication * results are known. Note that this happens asynchronously from (but shortly after) * {@link #onAuthenticated(BiometricAuthenticator.Identifier, boolean, ArrayList)} and allows * {@link CoexCoordinator} a chance to invoke/delay this event. * @param authenticated */ protected abstract void handleLifecycleAfterAuth(boolean authenticated); public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, @NonNull String owner, Loading Loading @@ -221,7 +249,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } final CoexCoordinator coordinator = CoexCoordinator.getInstance(); coordinator.onAuthenticationSucceeded(this, new CoexCoordinator.Callback() { coordinator.onAuthenticationSucceeded(SystemClock.uptimeMillis(), this, new CoexCoordinator.Callback() { @Override public void sendAuthenticationResult(boolean addAuthTokenIfStrong) { if (addAuthTokenIfStrong && mIsStrongBiometric) { Loading Loading @@ -262,6 +291,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> vibrateSuccess(); } } @Override public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */); } }); } else { // Allow system-defined limit of number of attempts before giving up Loading @@ -272,7 +306,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } final CoexCoordinator coordinator = CoexCoordinator.getInstance(); coordinator.onAuthenticationRejected(this, lockoutMode, coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode, new CoexCoordinator.Callback() { @Override public void sendAuthenticationResult(boolean addAuthTokenIfStrong) { Loading @@ -291,6 +325,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> vibrateError(); } } @Override public void handleLifecycleAfterAuth() { AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */); } }); } } Loading @@ -307,6 +346,12 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } } @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); mState = STATE_STOPPED; } /** * Start authentication */ Loading Loading @@ -345,6 +390,10 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> } } public @State int getState() { return mState; } @Override public int getProtoEnum() { return BiometricsProto.CM_AUTHENTICATE; Loading
services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +0 −1 Original line number Diff line number Diff line Loading @@ -355,7 +355,6 @@ public class BiometricScheduler { /** * Creates a new scheduler. * @param context system_server context. * @param tag for the specific instance of the scheduler. Should be unique. * @param sensorType the sensorType that this scheduler is handling. * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures Loading
services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +182 −10 Original line number Diff line number Diff line Loading @@ -22,12 +22,17 @@ import static com.android.server.biometrics.sensors.BiometricScheduler.sensorTyp import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.sensors.BiometricScheduler.SensorType; import com.android.server.biometrics.sensors.fingerprint.Udfps; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; /** Loading @@ -43,6 +48,9 @@ public class CoexCoordinator { "com.android.server.biometrics.sensors.CoexCoordinator.enable"; private static final boolean DEBUG = true; // Successful authentications should be used within this amount of time. static final long SUCCESSFUL_AUTH_VALID_DURATION_MS = 5000; /** * Callback interface notifying the owner of "results" from the CoexCoordinator's business * logic. Loading @@ -58,10 +66,69 @@ public class CoexCoordinator { * Requests the owner to initiate a vibration for this event. */ void sendHapticFeedback(); /** * Requests the owner to handle the AuthenticationClient's lifecycle (e.g. finish and remove * from scheduler if auth was successful). */ void handleLifecycleAfterAuth(); } private static CoexCoordinator sInstance; @VisibleForTesting public static class SuccessfulAuth { final long mAuthTimestamp; final @SensorType int mSensorType; final AuthenticationClient<?> mAuthenticationClient; final Callback mCallback; final CleanupRunnable mCleanupRunnable; public static class CleanupRunnable implements Runnable { @NonNull final LinkedList<SuccessfulAuth> mSuccessfulAuths; @NonNull final SuccessfulAuth mAuth; @NonNull final Callback mCallback; public CleanupRunnable(@NonNull LinkedList<SuccessfulAuth> successfulAuths, @NonNull SuccessfulAuth auth, @NonNull Callback callback) { mSuccessfulAuths = successfulAuths; mAuth = auth; mCallback = callback; } @Override public void run() { final boolean removed = mSuccessfulAuths.remove(mAuth); Slog.w(TAG, "Removing stale successfulAuth: " + mAuth.toString() + ", success: " + removed); mCallback.handleLifecycleAfterAuth(); } } public SuccessfulAuth(@NonNull Handler handler, @NonNull LinkedList<SuccessfulAuth> successfulAuths, long currentTimeMillis, @SensorType int sensorType, @NonNull AuthenticationClient<?> authenticationClient, @NonNull Callback callback) { mAuthTimestamp = currentTimeMillis; mSensorType = sensorType; mAuthenticationClient = authenticationClient; mCallback = callback; mCleanupRunnable = new CleanupRunnable(successfulAuths, this, callback); handler.postDelayed(mCleanupRunnable, SUCCESSFUL_AUTH_VALID_DURATION_MS); } @Override public String toString() { return "SensorType: " + sensorTypeToString(mSensorType) + ", mAuthTimestamp: " + mAuthTimestamp + ", authenticationClient: " + mAuthenticationClient; } } /** * @return a singleton instance. */ Loading @@ -85,11 +152,15 @@ public class CoexCoordinator { // SensorType to AuthenticationClient map private final Map<Integer, AuthenticationClient<?>> mClientMap; @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths; private boolean mAdvancedLogicEnabled; private final Handler mHandler; private CoexCoordinator() { // Singleton mClientMap = new HashMap<>(); mSuccessfulAuths = new LinkedList<>(); mHandler = new Handler(Looper.getMainLooper()); } public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType, Loading Loading @@ -121,34 +192,43 @@ public class CoexCoordinator { mClientMap.remove(sensorType); } public void onAuthenticationSucceeded(@NonNull AuthenticationClient<?> client, public void onAuthenticationSucceeded(long currentTimeMillis, @NonNull AuthenticationClient<?> client, @NonNull Callback callback) { if (client.isBiometricPrompt()) { callback.sendHapticFeedback(); // For BP, BiometricService will add the authToken to Keystore. callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } else if (isUnknownClient(client)) { // Client doesn't exist in our map for some reason. Give the user feedback so the // device doesn't feel like it's stuck. All other cases below can assume that the // client exists in our map. callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } else if (mAdvancedLogicEnabled && client.isKeyguard()) { if (isSingleAuthOnly(client)) { // Single sensor authentication callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } else { // Multi sensor authentication AuthenticationClient<?> udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null); AuthenticationClient<?> face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); if (isCurrentFaceAuth(client)) { if (isPointerDown(udfps)) { // Face auth success while UDFPS pointer down. No callback, no haptic. // Feedback will be provided after UDFPS result. if (isUdfpsActivelyAuthing(udfps)) { // Face auth success while UDFPS is actively authing. No callback, no haptic // Feedback will be provided after UDFPS result: // 1) UDFPS succeeds - simply remove this from the queue // 2) UDFPS rejected - use this face auth success to notify clients mSuccessfulAuths.add(new SuccessfulAuth(mHandler, mSuccessfulAuths, currentTimeMillis, SENSOR_TYPE_FACE, client, callback)); } else { callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } } else if (isCurrentUdfps(client)) { if (isFaceScanning()) { Loading @@ -156,8 +236,12 @@ public class CoexCoordinator { // Cancel face auth and/or prevent it from invoking haptics/callbacks after face.cancel(); } removeAndFinishAllFaceFromQueue(); callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } } } else { Loading @@ -165,13 +249,68 @@ public class CoexCoordinator { // FingerprintManager for highlighting fingers callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } } public void onAuthenticationRejected(@NonNull AuthenticationClient<?> client, public void onAuthenticationRejected(long currentTimeMillis, @NonNull AuthenticationClient<?> client, @LockoutTracker.LockoutMode int lockoutMode, @NonNull Callback callback) { final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard(); if (keyguardAdvancedLogic) { if (isSingleAuthOnly(client)) { callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } else { // Multi sensor authentication 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(); } else if (isCurrentUdfps(client)) { // Face should either be running, or have already finished SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis); if (auth != null) { Slog.d(TAG, "Using recent auth: " + auth); callback.handleLifecycleAfterAuth(); auth.mCallback.sendHapticFeedback(); auth.mCallback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); auth.mCallback.handleLifecycleAfterAuth(); } else if (isFaceScanning()) { // UDFPS rejected but face is still scanning Slog.d(TAG, "UDFPS rejected in multi-sensor auth, face: " + face); callback.handleLifecycleAfterAuth(); // TODO(b/193089985): Enforce/ensure that face auth finishes (whether // accept/reject) within X amount of time. Otherwise users will be stuck // waiting with their finger down for a long time. } else { // Face not scanning, and was not found in the queue. Most likely, face // auth was too long ago. Slog.d(TAG, "UDFPS rejected in multi-sensor auth, face not scanning"); callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } } else { Slog.d(TAG, "Unknown client rejected: " + client); callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } } } else { callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } // Always notify keyguard, otherwise the cached "running" state in KeyguardUpdateMonitor // will get stuck. if (lockoutMode == LockoutTracker.LOCKOUT_NONE) { // Don't send onAuthenticationFailed if we're in lockout, it causes a // janky UI on Keyguard/BiometricPrompt since "authentication failed" Loading @@ -180,6 +319,30 @@ public class CoexCoordinator { } } @Nullable private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) { for (SuccessfulAuth auth : mSuccessfulAuths) { if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) { Slog.d(TAG, "Removing stale auth: " + auth); mSuccessfulAuths.remove(auth); } else if (auth.mSensorType == SENSOR_TYPE_FACE) { mSuccessfulAuths.remove(auth); return auth; } } return null; } private void removeAndFinishAllFaceFromQueue() { for (SuccessfulAuth auth : mSuccessfulAuths) { if (auth.mSensorType == SENSOR_TYPE_FACE) { Slog.d(TAG, "Removing from queue and finishing: " + auth); auth.mCallback.handleLifecycleAfterAuth(); mSuccessfulAuths.remove(auth); } } } private boolean isCurrentFaceAuth(@NonNull AuthenticationClient<?> client) { return client == mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); } Loading @@ -189,12 +352,13 @@ public class CoexCoordinator { } private boolean isFaceScanning() { return mClientMap.containsKey(SENSOR_TYPE_FACE); AuthenticationClient<?> client = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); return client != null && client.getState() == AuthenticationClient.STATE_STARTED; } private static boolean isPointerDown(@Nullable AuthenticationClient<?> client) { private static boolean isUdfpsActivelyAuthing(@Nullable AuthenticationClient<?> client) { if (client instanceof Udfps) { return ((Udfps) client).isPointerDown(); return client.getState() == AuthenticationClient.STATE_STARTED; } return false; } Loading @@ -221,7 +385,15 @@ public class CoexCoordinator { return true; } @Override public String toString() { return "Enabled: " + mAdvancedLogicEnabled; StringBuilder sb = new StringBuilder(); sb.append("Enabled: ").append(mAdvancedLogicEnabled); sb.append(", Queue size: " ).append(mSuccessfulAuths.size()); for (SuccessfulAuth auth : mSuccessfulAuths) { sb.append(", Auth: ").append(auth.toString()); } return sb.toString(); } }
services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +16 −6 Original line number Diff line number Diff line Loading @@ -89,6 +89,12 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements R.array.config_face_acquire_vendor_keyguard_ignorelist); } @Override public void start(@NonNull Callback callback) { super.start(callback); mState = STATE_STARTED; } @NonNull @Override protected Callback wrapCallbackForStart(@NonNull Callback callback) { Loading Loading @@ -127,11 +133,21 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements && mLastAcquire != FaceManager.FACE_ACQUIRED_UNKNOWN; } @Override protected void handleLifecycleAfterAuth(boolean authenticated) { // For face, the authentication lifecycle ends either when // 1) Authenticated == true // 2) Error occurred // 3) Authenticated == false mCallback.onClientFinished(this, true /* success */); } @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> token) { super.onAuthenticated(identifier, authenticated, token); mState = STATE_STOPPED; mUsageStats.addEvent(new UsageStats.AuthenticationEvent( getStartTimeMs(), System.currentTimeMillis() - getStartTimeMs() /* latency */, Loading @@ -139,12 +155,6 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements 0 /* error */, 0 /* vendorError */, getTargetUserId())); // For face, the authentication lifecycle ends either when // 1) Authenticated == true // 2) Error occurred // 3) Authenticated == false mCallback.onClientFinished(this, true /* success */); } @Override Loading