diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index fed320d3cf237..6b7787aad17e3 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -73,6 +73,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; +import com.android.server.biometrics.sensors.CoexCoordinator; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -1100,6 +1101,12 @@ public class BiometricService extends SystemService { } return new ArrayList<>(); } + + public boolean isAdvancedCoexLogicEnabled(Context context) { + return (Build.IS_USERDEBUG || Build.IS_ENG) + && Settings.Secure.getInt(context.getContentResolver(), + CoexCoordinator.SETTING_ENABLE_NAME, 0) != 0; + } } /** @@ -1126,6 +1133,12 @@ public class BiometricService extends SystemService { mSettingObserver = mInjector.getSettingObserver(context, mHandler, mEnabledOnKeyguardCallbacks); + // TODO(b/193089985) This logic lives here (outside of CoexCoordinator) so that it doesn't + // need to depend on context. We can remove this code once the advanced logic is enabled + // by default. + CoexCoordinator coexCoordinator = CoexCoordinator.getInstance(); + coexCoordinator.setAdvancedLogicEnabled(injector.isAdvancedCoexLogicEnabled(context)); + try { injector.getActivityManagerService().registerUserSwitchObserver( new UserSwitchObserver() { @@ -1437,5 +1450,7 @@ public class BiometricService extends SystemService { pw.println(); pw.println("CurrentSession: " + mCurrentAuthSession); pw.println(); + pw.println("CoexCoordinator: " + CoexCoordinator.getInstance().toString()); + pw.println(); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 61266071f7889..bfd4d6e8fe5da 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -359,7 +359,8 @@ public class BiometricScheduler { * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures * (such as fingerprint swipe). */ - public BiometricScheduler(@NonNull String tag, @SensorType int sensorType, + public BiometricScheduler(@NonNull String tag, + @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS, 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 08bf2e020f75d..18e9cb16e2660 100644 --- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java @@ -16,11 +16,17 @@ package com.android.server.biometrics.sensors; +import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_FACE; +import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_UDFPS; import static com.android.server.biometrics.sensors.BiometricScheduler.sensorTypeToString; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.sensors.fingerprint.Udfps; + import java.util.HashMap; import java.util.Map; @@ -33,6 +39,8 @@ import java.util.Map; public class CoexCoordinator { private static final String TAG = "BiometricCoexCoordinator"; + public static final String SETTING_ENABLE_NAME = + "com.android.server.biometrics.sensors.CoexCoordinator.enable"; private static final boolean DEBUG = true; /** @@ -54,16 +62,30 @@ public class CoexCoordinator { private static CoexCoordinator sInstance; + /** + * @return a singleton instance. + */ @NonNull - static CoexCoordinator getInstance() { + public static CoexCoordinator getInstance() { if (sInstance == null) { sInstance = new CoexCoordinator(); } return sInstance; } + @VisibleForTesting + public void setAdvancedLogicEnabled(boolean enabled) { + mAdvancedLogicEnabled = enabled; + } + + @VisibleForTesting + void reset() { + mClientMap.clear(); + } + // SensorType to AuthenticationClient map private final Map> mClientMap; + private boolean mAdvancedLogicEnabled; private CoexCoordinator() { // Singleton @@ -105,8 +127,33 @@ public class CoexCoordinator { callback.sendHapticFeedback(); // For BP, BiometricService will add the authToken to Keystore. callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */); + } 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 */); + } else if (mAdvancedLogicEnabled && client.isKeyguard()) { + if (isSingleAuthOnly(client)) { + // Single sensor authentication + callback.sendHapticFeedback(); + callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); + } else { + // Multi sensor authentication + AuthenticationClient udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, 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. + } else { + callback.sendHapticFeedback(); + callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); + } + } + } } else { - // Keyguard, FingerprintManager, FaceManager, etc + // Non-keyguard authentication. For example, Fingerprint Settings use of + // FingerprintManager for highlighting fingers callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); } @@ -123,4 +170,41 @@ public class CoexCoordinator { callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */); } } + + private boolean isCurrentFaceAuth(@NonNull AuthenticationClient client) { + return client == mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); + } + + private boolean isPointerDown(@Nullable AuthenticationClient client) { + if (client instanceof Udfps) { + return ((Udfps) client).isPointerDown(); + } + return false; + } + + private boolean isUnknownClient(@NonNull AuthenticationClient client) { + for (AuthenticationClient c : mClientMap.values()) { + if (c == client) { + return false; + } + } + return true; + } + + private boolean isSingleAuthOnly(@NonNull AuthenticationClient client) { + if (mClientMap.values().size() != 1) { + return false; + } + + for (AuthenticationClient c : mClientMap.values()) { + if (c != client) { + return false; + } + } + return true; + } + + public String toString() { + return "Enabled: " + mAdvancedLogicEnabled; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 4abd402a4da8e..206b8f0779e8e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -494,8 +494,8 @@ public class Sensor { mToken = new Binder(); mHandler = handler; mSensorProperties = sensorProperties; - mScheduler = new UserAwareBiometricScheduler(tag, BiometricScheduler.SENSOR_TYPE_FACE, - null /* gestureAvailabilityDispatcher */, + mScheduler = new UserAwareBiometricScheduler(tag, + BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, new UserAwareBiometricScheduler.UserSwitchCallback() { @NonNull diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java index 150e69c60974a..a2c07515156f5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java @@ -27,4 +27,5 @@ public interface Udfps { void onPointerDown(int x, int y, float minor, float major); void onPointerUp(); void onUiReady(); + boolean isPointerDown(); } 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 8681ad75b7c65..639814bf549fc 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 @@ -55,6 +55,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient imp @NonNull private final LockoutCache mLockoutCache; @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; @Nullable private ICancellationSignal mCancellationSignal; + private boolean mIsPointerDown; FingerprintAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon lazyDaemon, @NonNull IBinder token, @@ -143,6 +144,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient imp @Override public void onPointerDown(int x, int y, float minor, float major) { try { + mIsPointerDown = true; getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major); if (getListener() != null) { getListener().onUdfpsPointerDown(getSensorId()); @@ -155,6 +157,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient imp @Override public void onPointerUp() { try { + mIsPointerDown = false; getFreshDaemon().onPointerUp(0 /* pointerId */); if (getListener() != null) { getListener().onUdfpsPointerUp(getSensorId()); @@ -164,6 +167,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient imp } } + @Override + public boolean isPointerDown() { + return mIsPointerDown; + } + @Override public void onUiReady() { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index a211bb5e14e38..e8200af68db52 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -55,6 +55,7 @@ class FingerprintEnrollClient extends EnrollClient implements Udfps { private final @FingerprintManager.EnrollReason int mEnrollReason; @Nullable private ICancellationSignal mCancellationSignal; private final int mMaxTemplatesPerUser; + private boolean mIsPointerDown; FingerprintEnrollClient(@NonNull Context context, @NonNull LazyDaemon lazyDaemon, @NonNull IBinder token, @@ -167,6 +168,7 @@ class FingerprintEnrollClient extends EnrollClient implements Udfps { @Override public void onPointerDown(int x, int y, float minor, float major) { try { + mIsPointerDown = true; getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major); } catch (RemoteException e) { Slog.e(TAG, "Unable to send pointer down", e); @@ -176,12 +178,18 @@ class FingerprintEnrollClient extends EnrollClient implements Udfps { @Override public void onPointerUp() { try { + mIsPointerDown = false; getFreshDaemon().onPointerUp(0 /* pointerId */); } catch (RemoteException e) { Slog.e(TAG, "Unable to send pointer up", e); } } + @Override + public boolean isPointerDown() { + return mIsPointerDown; + } + @Override public void onUiReady() { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 312c52c4a844b..24ce8676235a7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -138,7 +138,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage TestableBiometricScheduler(@NonNull String tag, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher); + super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, + gestureAvailabilityDispatcher); mInternalCallback = new TestableInternalCallback(); } 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 40e3bc3a46986..95a54d3591a38 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 @@ -52,6 +52,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient lazyDaemon, @NonNull IBinder token, @@ -160,6 +161,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient private final boolean mIsStrongBiometric; @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; + private boolean mIsPointerDown; public FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon lazyDaemon, @NonNull IBinder token, @@ -99,14 +100,21 @@ class FingerprintDetectClient extends AcquisitionClient @Override public void onPointerDown(int x, int y, float minor, float major) { + mIsPointerDown = true; UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major); } @Override public void onPointerUp() { + mIsPointerDown = false; UdfpsHelper.onFingerUp(getFreshDaemon()); } + @Override + public boolean isPointerDown() { + return mIsPointerDown; + } + @Override public void onUiReady() { // Unsupported in HIDL. diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index eba445f7e7b43..250e1328104ba 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -52,6 +52,7 @@ public class FingerprintEnrollClient extends EnrollClient lazyDaemon, @NonNull IBinder token, @@ -157,14 +158,21 @@ public class FingerprintEnrollClient extends EnrollClient client = mock(AuthenticationClient.class); when(client.isBiometricPrompt()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); + mCoexCoordinator.onAuthenticationSucceeded(client, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); @@ -59,9 +75,13 @@ public class CoexCoordinatorTest { @Test public void testBiometricPrompt_authReject_whenNotLockedOut() { + mCoexCoordinator.reset(); + AuthenticationClient client = mock(AuthenticationClient.class); when(client.isBiometricPrompt()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); + mCoexCoordinator.onAuthenticationRejected(client, LockoutTracker.LOCKOUT_NONE, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); @@ -69,11 +89,69 @@ public class CoexCoordinatorTest { @Test public void testBiometricPrompt_authReject_whenLockedOut() { + mCoexCoordinator.reset(); + AuthenticationClient client = mock(AuthenticationClient.class); when(client.isBiometricPrompt()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); + mCoexCoordinator.onAuthenticationRejected(client, LockoutTracker.LOCKOUT_TIMED, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); } + + @Test + public void testKeyguard_faceAuthOnly_success() { + mCoexCoordinator.reset(); + + AuthenticationClient client = mock(AuthenticationClient.class); + when(client.isKeyguard()).thenReturn(true); + + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); + + mCoexCoordinator.onAuthenticationSucceeded(client, mCallback); + verify(mCallback).sendHapticFeedback(); + verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); + } + + @Test + public void testKeyguard_faceAuth_udfpsNotTouching_faceSuccess() { + mCoexCoordinator.reset(); + + AuthenticationClient faceClient = mock(AuthenticationClient.class); + when(faceClient.isKeyguard()).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.onAuthenticationSucceeded(faceClient, mCallback); + verify(mCallback).sendHapticFeedback(); + verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); + } + + @Test + public void testKeyguard_faceAuth_udfpsTouching_faceSuccess() { + mCoexCoordinator.reset(); + + AuthenticationClient faceClient = mock(AuthenticationClient.class); + when(faceClient.isKeyguard()).thenReturn(true); + + AuthenticationClient udfpsClient = mock(AuthenticationClient.class, + withSettings().extraInterfaces(Udfps.class)); + when(udfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true); + + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); + + mCoexCoordinator.onAuthenticationSucceeded(faceClient, mCallback); + verify(mCallback, never()).sendHapticFeedback(); + verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); + } }