From 0d09e5b0ba7f6365d79eafe92f4227f4228d9c85 Mon Sep 17 00:00:00 2001 From: Joshua McCloskey Date: Sun, 28 Aug 2022 04:11:36 +0000 Subject: [PATCH] Removed Coexcoordinator. Test: atest com.android.server.biometrics Test: Manually verified on dual biometric device, auth and BP work as expected. Fixes: 244083311 Change-Id: Ie87156f0a498103bd7f0e74d016de29d8c9e305b --- .../server/biometrics/BiometricService.java | 21 - .../sensors/AuthenticationClient.java | 222 ++----- .../sensors/BiometricScheduler.java | 18 +- .../biometrics/sensors/CoexCoordinator.java | 525 ---------------- .../sensors/UserAwareBiometricScheduler.java | 7 +- .../face/aidl/FaceAuthenticationClient.java | 2 - .../aidl/FingerprintAuthenticationClient.java | 2 - .../sensors/BiometricSchedulerTest.java | 3 +- .../sensors/CoexCoordinatorTest.java | 573 ------------------ .../UserAwareBiometricSchedulerTest.java | 3 +- .../sensors/face/aidl/SensorTest.java | 4 +- .../sensors/fingerprint/aidl/SensorTest.java | 4 +- 12 files changed, 76 insertions(+), 1308 deletions(-) delete mode 100644 services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java delete mode 100644 services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 689ddd2fb9ee2..c29755aaa845b 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -72,7 +72,6 @@ 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; @@ -951,16 +950,6 @@ public class BiometricService extends SystemService { return new ArrayList<>(); } - public boolean isAdvancedCoexLogicEnabled(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - CoexCoordinator.SETTING_ENABLE_NAME, 1) != 0; - } - - public boolean isCoexFaceNonBypassHapticsDisabled(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - CoexCoordinator.FACE_HAPTIC_DISABLE, 0) != 0; - } - public Supplier getRequestGenerator() { final AtomicLong generator = new AtomicLong(0); return () -> generator.incrementAndGet(); @@ -992,14 +981,6 @@ public class BiometricService extends SystemService { mEnabledOnKeyguardCallbacks); mRequestCounter = mInjector.getRequestGenerator(); - // 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)); - coexCoordinator.setFaceHapticDisabledWhenNonBypass( - injector.isCoexFaceNonBypassHapticsDisabled(context)); - try { injector.getActivityManagerService().registerUserSwitchObserver( new UserSwitchObserver() { @@ -1333,7 +1314,5 @@ public class BiometricService extends SystemService { pw.println(); pw.println("CurrentSession: " + mAuthSession); pw.println(); - pw.println("CoexCoordinator: " + CoexCoordinator.getInstance().toString()); - pw.println(); } } 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 4eb6d38d92274..8a24ff6cffded 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -29,7 +29,6 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricOverlayConstants; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.security.KeyStore; import android.util.EventLog; import android.util.Slog; @@ -46,9 +45,7 @@ import java.util.function.Supplier; * A class to keep track of the authentication state for a given client. */ public abstract class AuthenticationClient extends AcquisitionClient - implements AuthenticationConsumer { - - private static final String TAG = "Biometrics/AuthenticationClient"; + implements AuthenticationConsumer { // New, has not started yet public static final int STATE_NEW = 0; @@ -67,28 +64,27 @@ public abstract class AuthenticationClient extends AcquisitionClient STATE_STARTED_PAUSED_ATTEMPTED, STATE_STOPPED}) @interface State {} - + private static final String TAG = "Biometrics/AuthenticationClient"; + protected final long mOperationId; private final boolean mIsStrongBiometric; private final boolean mRequireConfirmation; private final ActivityTaskManager mActivityTaskManager; private final BiometricManager mBiometricManager; - @Nullable private final TaskStackListener mTaskStackListener; + @Nullable + private final TaskStackListener mTaskStackListener; private final LockoutTracker mLockoutTracker; private final boolean mIsRestricted; private final boolean mAllowBackgroundAuthentication; private final boolean mIsKeyguardBypassEnabled; - - protected final long mOperationId; - + // 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. + @State + protected int mState = STATE_NEW; private long mStartTimeMs; private boolean mAuthAttempted; private boolean mAuthSuccess = false; - // 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; - public AuthenticationClient(@NonNull Context context, @NonNull Supplier lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, @NonNull String owner, @@ -111,8 +107,9 @@ public abstract class AuthenticationClient extends AcquisitionClient mIsKeyguardBypassEnabled = isKeyguardBypassEnabled; } - public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) { - final @LockoutTracker.LockoutMode int lockoutMode = + @LockoutTracker.LockoutMode + public int handleFailedAttempt(int userId) { + @LockoutTracker.LockoutMode final int lockoutMode = mLockoutTracker.getLockoutModeForUser(userId); final PerformanceTracker performanceTracker = PerformanceTracker.getInstanceForSensorId(getSensorId()); @@ -173,14 +170,16 @@ public abstract class AuthenticationClient extends AcquisitionClient final ClientMonitorCallbackConverter listener = getListener(); - if (DEBUG) Slog.v(TAG, "onAuthenticated(" + authenticated + ")" - + ", ID:" + identifier.getBiometricId() - + ", Owner: " + getOwnerString() - + ", isBP: " + isBiometricPrompt() - + ", listener: " + listener - + ", requireConfirmation: " + mRequireConfirmation - + ", user: " + getTargetUserId() - + ", clientMonitor: " + toString()); + if (DEBUG) { + Slog.v(TAG, "onAuthenticated(" + authenticated + ")" + + ", ID:" + identifier.getBiometricId() + + ", Owner: " + getOwnerString() + + ", isBP: " + isBiometricPrompt() + + ", listener: " + listener + + ", requireConfirmation: " + mRequireConfirmation + + ", user: " + getTargetUserId() + + ", clientMonitor: " + this); + } final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId()); if (isCryptoOperation()) { @@ -239,142 +238,57 @@ public abstract class AuthenticationClient extends AcquisitionClient getSensorId(), getTargetUserId(), byteToken); } - final CoexCoordinator coordinator = CoexCoordinator.getInstance(); - coordinator.onAuthenticationSucceeded(SystemClock.uptimeMillis(), this, - new CoexCoordinator.Callback() { - @Override - public void sendAuthenticationResult(boolean addAuthTokenIfStrong) { - if (addAuthTokenIfStrong && mIsStrongBiometric) { - final int result = KeyStore.getInstance().addAuthToken(byteToken); - Slog.d(TAG, "addAuthToken: " + result); + // For BP, BiometricService will add the authToken to Keystore. + if (!isBiometricPrompt() && mIsStrongBiometric) { + final int result = KeyStore.getInstance().addAuthToken(byteToken); + if (result != KeyStore.NO_ERROR) { + Slog.d(TAG, "Error adding auth token : " + result); + } else { + Slog.d(TAG, "addAuthToken: " + result); + } + } else { + Slog.d(TAG, "Skipping addAuthToken"); + } + try { + if (listener != null) { + if (!mIsRestricted) { + listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken, + getTargetUserId(), mIsStrongBiometric); } else { - Slog.d(TAG, "Skipping addAuthToken"); - } - - if (listener != null) { - try { - // Explicitly have if/else here to make it super obvious in case the - // code is touched in the future. - if (!mIsRestricted) { - listener.onAuthenticationSucceeded(getSensorId(), - identifier, - byteToken, - getTargetUserId(), - mIsStrongBiometric); - } else { - listener.onAuthenticationSucceeded(getSensorId(), - null /* identifier */, - byteToken, - getTargetUserId(), - mIsStrongBiometric); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to notify listener", e); - } - } else { - Slog.w(TAG, "Client not listening"); + listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */, + byteToken, + getTargetUserId(), mIsStrongBiometric); } + } else { + Slog.e(TAG, "Received successful auth, but client was not listening"); } - - @Override - public void sendHapticFeedback() { - if (listener != null && mShouldVibrate) { - vibrateSuccess(); - } - } - - @Override - public void handleLifecycleAfterAuth() { - AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */); - } - - @Override - public void sendAuthenticationCanceled() { - sendCancelOnly(listener); - } - }); - } else { // not authenticated + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify listener", e); + mCallback.onClientFinished(this, false); + return; + } + } else { if (isBackgroundAuth) { Slog.e(TAG, "cancelling due to background auth"); cancel(); } else { // Allow system-defined limit of number of attempts before giving up - final @LockoutTracker.LockoutMode int lockoutMode = + @LockoutTracker.LockoutMode final int lockoutMode = handleFailedAttempt(getTargetUserId()); if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { markAlreadyDone(); } - final CoexCoordinator coordinator = CoexCoordinator.getInstance(); - coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode, - new CoexCoordinator.Callback() { - @Override - public void sendAuthenticationResult(boolean addAuthTokenIfStrong) { - if (listener != null) { - try { - listener.onAuthenticationFailed(getSensorId()); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to notify listener", e); - } - } - } - - @Override - public void sendHapticFeedback() { - if (listener != null && mShouldVibrate) { - vibrateError(); - } - } - - @Override - public void handleLifecycleAfterAuth() { - AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */); - } - - @Override - public void sendAuthenticationCanceled() { - sendCancelOnly(listener); - } - }); + try { + listener.onAuthenticationFailed(getSensorId()); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify listener", e); + mCallback.onClientFinished(this, false); + return; + } } } - } - - /** - * Only call this method on interfaces where lockout does not come from onError, I.E. the - * old HIDL implementation. - */ - protected void onLockoutTimed(long durationMillis) { - final ClientMonitorCallbackConverter listener = getListener(); - final CoexCoordinator coordinator = CoexCoordinator.getInstance(); - coordinator.onAuthenticationError(this, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, - new CoexCoordinator.ErrorCallback() { - @Override - public void sendHapticFeedback() { - if (listener != null && mShouldVibrate) { - vibrateError(); - } - } - }); - } - - /** - * Only call this method on interfaces where lockout does not come from onError, I.E. the - * old HIDL implementation. - */ - protected void onLockoutPermanent() { - final ClientMonitorCallbackConverter listener = getListener(); - final CoexCoordinator coordinator = CoexCoordinator.getInstance(); - coordinator.onAuthenticationError(this, - BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT, - new CoexCoordinator.ErrorCallback() { - @Override - public void sendHapticFeedback() { - if (listener != null && mShouldVibrate) { - vibrateError(); - } - } - }); + AuthenticationClient.this.handleLifecycleAfterAuth(authenticated); } private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) { @@ -396,7 +310,7 @@ public abstract class AuthenticationClient extends AcquisitionClient public void onAcquired(int acquiredInfo, int vendorCode) { super.onAcquired(acquiredInfo, vendorCode); - final @LockoutTracker.LockoutMode int lockoutMode = + @LockoutTracker.LockoutMode final int lockoutMode = mLockoutTracker.getLockoutModeForUser(getTargetUserId()); if (lockoutMode == LockoutTracker.LOCKOUT_NONE) { PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); @@ -408,8 +322,6 @@ public abstract class AuthenticationClient extends AcquisitionClient public void onError(@BiometricConstants.Errors int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); mState = STATE_STOPPED; - - CoexCoordinator.getInstance().onAuthenticationError(this, errorCode, this::vibrateError); } /** @@ -419,7 +331,7 @@ public abstract class AuthenticationClient extends AcquisitionClient public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); - final @LockoutTracker.LockoutMode int lockoutMode = + @LockoutTracker.LockoutMode final int lockoutMode = mLockoutTracker.getLockoutModeForUser(getTargetUserId()); if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { Slog.v(TAG, "In lockout mode(" + lockoutMode + ") ; disallowing authentication"); @@ -450,22 +362,20 @@ public abstract class AuthenticationClient extends AcquisitionClient } /** - * 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 + * Handles lifecycle, e.g. {@link BiometricScheduler} after authentication. This is necessary + * as different clients handle the lifecycle of authentication success/reject differently. I.E. + * Fingerprint does not finish authentication when it is rejected. */ protected abstract void handleLifecycleAfterAuth(boolean authenticated); /** * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched. - * etc) + * etc) */ public abstract boolean wasUserDetected(); - public @State int getState() { + @State + public int getState() { return mState; } 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 63609f77dc75b..9317c4ec12b59 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -54,7 +54,7 @@ import java.util.function.Consumer; * interactions with the HAL before finishing. * * We currently assume (and require) that each biometric sensor have its own instance of a - * {@link BiometricScheduler}. See {@link CoexCoordinator}. + * {@link BiometricScheduler}. */ @MainThread public class BiometricScheduler { @@ -156,7 +156,6 @@ public class BiometricScheduler { private int mTotalOperationsHandled; private final int mRecentOperationsLimit; @NonNull private final List mRecentOperations; - @NonNull private final CoexCoordinator mCoexCoordinator; // Internal callback, notified when an operation is complete. Notifies the requester // that the operation is complete, before performing internal scheduler work (such as @@ -165,11 +164,6 @@ public class BiometricScheduler { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { Slog.d(getTag(), "[Started] " + clientMonitor); - - if (clientMonitor instanceof AuthenticationClient) { - mCoexCoordinator.addAuthenticationClient(mSensorType, - (AuthenticationClient) clientMonitor); - } } @Override @@ -189,10 +183,6 @@ public class BiometricScheduler { } Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success); - if (clientMonitor instanceof AuthenticationClient) { - mCoexCoordinator.removeAuthenticationClient(mSensorType, - (AuthenticationClient) clientMonitor); - } if (mGestureAvailabilityDispatcher != null) { mGestureAvailabilityDispatcher.markSensorActive( @@ -216,8 +206,7 @@ public class BiometricScheduler { @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, - int recentOperationsLimit, - @NonNull CoexCoordinator coexCoordinator) { + int recentOperationsLimit) { mBiometricTag = tag; mHandler = handler; mSensorType = sensorType; @@ -227,7 +216,6 @@ public class BiometricScheduler { mCrashStates = new ArrayDeque<>(); mRecentOperationsLimit = recentOperationsLimit; mRecentOperations = new ArrayList<>(); - mCoexCoordinator = coexCoordinator; } /** @@ -244,7 +232,7 @@ public class BiometricScheduler { this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), - LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance()); + LOG_NUM_RECENT_OPERATIONS); } @VisibleForTesting diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java deleted file mode 100644 index c8a90e7a564b3..0000000000000 --- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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.hardware.biometrics.BiometricConstants; -import android.os.Handler; -import android.os.Looper; -import android.util.Slog; - -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; - -/** - * Singleton that contains the core logic for determining if haptics and authentication callbacks - * should be sent to receivers. Note that this class is used even when coex is not required (e.g. - * single sensor devices, or multi-sensor devices where only a single sensor is authenticating). - * This allows us to have all business logic in one testable place. - */ -public class CoexCoordinator { - - private static final String TAG = "BiometricCoexCoordinator"; - public static final String SETTING_ENABLE_NAME = - "com.android.server.biometrics.sensors.CoexCoordinator.enable"; - public static final String FACE_HAPTIC_DISABLE = - "com.android.server.biometrics.sensors.CoexCoordinator.disable_face_haptics"; - 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 for accept and reject. - */ - interface Callback { - /** - * Requests the owner to send the result (success/reject) and any associated info to the - * receiver (e.g. keyguard, BiometricService, etc). - */ - void sendAuthenticationResult(boolean addAuthTokenIfStrong); - - /** - * 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(); - - /** - * Requests the owner to notify the caller that authentication was canceled. - */ - 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 final CoexCoordinator sInstance = new CoexCoordinator(); - - @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 mSuccessfulAuths; - @NonNull final SuccessfulAuth mAuth; - @NonNull final Callback mCallback; - - public CleanupRunnable(@NonNull LinkedList 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 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; - } - } - - /** The singleton instance. */ - @NonNull - public static CoexCoordinator getInstance() { - return sInstance; - } - - @VisibleForTesting - public void setAdvancedLogicEnabled(boolean enabled) { - mAdvancedLogicEnabled = enabled; - } - - public void setFaceHapticDisabledWhenNonBypass(boolean disabled) { - mFaceHapticDisabledWhenNonBypass = disabled; - } - - @VisibleForTesting - void reset() { - mClientMap.clear(); - } - - // SensorType to AuthenticationClient map - private final Map> mClientMap = new HashMap<>(); - @VisibleForTesting final LinkedList mSuccessfulAuths = new LinkedList<>(); - private boolean mAdvancedLogicEnabled; - private boolean mFaceHapticDisabledWhenNonBypass; - private final Handler mHandler = new Handler(Looper.getMainLooper()); - - private CoexCoordinator() {} - - public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType, - @NonNull AuthenticationClient client) { - if (DEBUG) { - Slog.d(TAG, "addAuthenticationClient(" + sensorTypeToString(sensorType) + ")" - + ", client: " + client); - } - - if (mClientMap.containsKey(sensorType)) { - Slog.w(TAG, "Overwriting existing client: " + mClientMap.get(sensorType) - + " with new client: " + client); - } - - mClientMap.put(sensorType, client); - } - - public void removeAuthenticationClient(@BiometricScheduler.SensorType int sensorType, - @NonNull AuthenticationClient client) { - if (DEBUG) { - Slog.d(TAG, "removeAuthenticationClient(" + sensorTypeToString(sensorType) + ")" - + ", client: " + client); - } - - if (!mClientMap.containsKey(sensorType)) { - Slog.e(TAG, "sensorType: " + sensorType + " does not exist in map. Client: " + client); - return; - } - mClientMap.remove(sensorType); - } - - /** - * Notify the coordinator that authentication succeeded (accepted) - */ - public void onAuthenticationSucceeded(long currentTimeMillis, - @NonNull AuthenticationClient client, - @NonNull Callback callback) { - final boolean isUsingSingleModality = isSingleAuthOnly(client); - - if (client.isBiometricPrompt()) { - if (!isUsingSingleModality && hasMultipleSuccessfulAuthentications()) { - // only send feedback on the first one - } else { - 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 (isUsingSingleModality) { - // 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 (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 { - if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) { - Slog.w(TAG, "Skipping face success haptic"); - } else { - callback.sendHapticFeedback(); - } - callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); - callback.handleLifecycleAfterAuth(); - } - } else if (isCurrentUdfps(client)) { - if (isFaceScanning()) { - // UDFPS succeeds while face is still scanning - // Cancel face auth and/or prevent it from invoking haptics/callbacks after - face.cancel(); - } - - removeAndFinishAllFaceFromQueue(); - - callback.sendHapticFeedback(); - callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); - callback.handleLifecycleAfterAuth(); - } else { - // Capacitive fingerprint sensor (or other) - callback.sendHapticFeedback(); - callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); - callback.handleLifecycleAfterAuth(); - } - } - } else { - // Non-keyguard authentication. For example, Fingerprint Settings use of - // FingerprintManager for highlighting fingers - callback.sendHapticFeedback(); - callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); - callback.handleLifecycleAfterAuth(); - } - } - - /** - * Notify the coordinator that a rejection has occurred. - */ - public void onAuthenticationRejected(long currentTimeMillis, - @NonNull AuthenticationClient client, - @LockoutTracker.LockoutMode int lockoutMode, - @NonNull Callback callback) { - final boolean isUsingSingleModality = isSingleAuthOnly(client); - - if (mAdvancedLogicEnabled && client.isKeyguard()) { - if (isUsingSingleModality) { - 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)) { - 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 if (isUdfpsAuthAttempted(udfps)) { - // If UDFPS is STATE_STARTED_PAUSED (e.g. finger rejected but can still - // auth after pointer goes down, it means UDFPS encountered a rejection. In - // this case, we need to play the final reject haptic since face auth is - // also done now. - callback.sendHapticFeedback(); - callback.handleLifecycleAfterAuth(); - } else { - // UDFPS auth has never been attempted. - if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) { - Slog.w(TAG, "Skipping face reject haptic"); - } else { - callback.sendHapticFeedback(); - } - 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 { - Slog.d(TAG, "UDFPS rejected in multi-sensor auth"); - callback.sendHapticFeedback(); - callback.handleLifecycleAfterAuth(); - } - } else { - Slog.d(TAG, "Unknown client rejected: " + client); - callback.sendHapticFeedback(); - callback.handleLifecycleAfterAuth(); - } - } - } else if (client.isBiometricPrompt() && !isUsingSingleModality) { - if (!isCurrentFaceAuth(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" - // will show briefly and be replaced by "device locked out" message. - callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */); - } - } - - /** - * Notify the coordinator that an error has occurred. - */ - public void onAuthenticationError(@NonNull AuthenticationClient client, - @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) { - final boolean isUsingSingleModality = isSingleAuthOnly(client); - - // 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 hapticSuppressedByCoex; - if (mAdvancedLogicEnabled && client.isKeyguard()) { - if (isUsingSingleModality) { - hapticSuppressedByCoex = false; - } else { - hapticSuppressedByCoex = isCurrentFaceAuth(client) - && !client.isKeyguardBypassEnabled(); - } - } else if (client.isBiometricPrompt() && !isUsingSingleModality) { - hapticSuppressedByCoex = isCurrentFaceAuth(client); - } else { - hapticSuppressedByCoex = false; - } - - // Combine and send feedback if appropriate - if (shouldUsuallyVibrate && !hapticSuppressedByCoex) { - callback.sendHapticFeedback(); - } else { - Slog.v(TAG, "no haptic shouldUsuallyVibrate: " + shouldUsuallyVibrate - + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex); - } - } - - @Nullable - private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) { - for (SuccessfulAuth auth : mSuccessfulAuths) { - if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) { - // 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); - } else if (auth.mSensorType == SENSOR_TYPE_FACE) { - mSuccessfulAuths.remove(auth); - return auth; - } - } - return null; - } - - 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) { - if (auth.mSensorType == SENSOR_TYPE_FACE) { - Slog.d(TAG, "Removing from queue, canceling, and finishing: " + auth); - auth.mCallback.sendAuthenticationCanceled(); - auth.mCallback.handleLifecycleAfterAuth(); - mSuccessfulAuths.remove(auth); - } - } - } - - private boolean isCurrentFaceAuth(@NonNull AuthenticationClient client) { - return client == mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); - } - - private boolean isCurrentUdfps(@NonNull AuthenticationClient client) { - return client == mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null); - } - - private boolean isFaceScanning() { - AuthenticationClient client = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null); - return client != null && client.getState() == AuthenticationClient.STATE_STARTED; - } - - private static boolean isUdfpsActivelyAuthing(@Nullable AuthenticationClient client) { - if (client instanceof Udfps) { - return client.getState() == AuthenticationClient.STATE_STARTED; - } - return false; - } - - private static boolean isUdfpsAuthAttempted(@Nullable AuthenticationClient client) { - if (client instanceof Udfps) { - return client.getState() == AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED; - } - 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; - } - - private boolean hasMultipleSuccessfulAuthentications() { - int count = 0; - for (AuthenticationClient c : mClientMap.values()) { - if (c.wasAuthSuccessful()) { - count++; - } - if (count > 1) { - return true; - } - } - return false; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Enabled: ").append(mAdvancedLogicEnabled); - sb.append(", Face Haptic Disabled: ").append(mFaceHapticDisabledWhenNonBypass); - sb.append(", Queue size: " ).append(mSuccessfulAuths.size()); - for (SuccessfulAuth auth : mSuccessfulAuths) { - sb.append(", Auth: ").append(auth.toString()); - } - - return sb.toString(); - } -} diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index ae75b7dcc1016..a486d16189fa6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -95,10 +95,9 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, @NonNull CurrentUserRetriever currentUserRetriever, - @NonNull UserSwitchCallback userSwitchCallback, - @NonNull CoexCoordinator coexCoordinator) { + @NonNull UserSwitchCallback userSwitchCallback) { super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService, - LOG_NUM_RECENT_OPERATIONS, coexCoordinator); + LOG_NUM_RECENT_OPERATIONS); mCurrentUserRetriever = currentUserRetriever; mUserSwitchCallback = userSwitchCallback; @@ -112,7 +111,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), - currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance()); + currentUserRetriever, userSwitchCallback); } @Override 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 d0c58fd0545f1..9e8aa9a913098 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 @@ -274,7 +274,6 @@ class FaceAuthenticationClient extends AuthenticationClient @Override public void onLockoutTimed(long durationMillis) { - super.onLockoutTimed(durationMillis); mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT; @@ -290,7 +289,6 @@ class FaceAuthenticationClient extends AuthenticationClient @Override public void onLockoutPermanent() { - super.onLockoutPermanent(); mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; 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 b530c8db3c67b..3ce6b67652348 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 @@ -424,7 +424,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient @Override public void onLockoutTimed(long durationMillis) { - super.onLockoutTimed(durationMillis); mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; @@ -448,7 +447,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient @Override public void onLockoutPermanent() { - super.onLockoutPermanent(); mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; 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 45e3b43732666..eb1314194aa37 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 @@ -91,8 +91,7 @@ public class BiometricSchedulerTest { mToken = new Binder(); mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()), BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */, - mBiometricService, LOG_NUM_RECENT_OPERATIONS, - CoexCoordinator.getInstance()); + mBiometricService, LOG_NUM_RECENT_OPERATIONS); } @Test 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 deleted file mode 100644 index abf992b6c6376..0000000000000 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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_FP_OTHER; -import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_UDFPS; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.hardware.biometrics.BiometricConstants; -import android.platform.test.annotations.Presubmit; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import com.android.server.biometrics.sensors.fingerprint.Udfps; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.LinkedList; - -@Presubmit -@SmallTest -public class CoexCoordinatorTest { - - @Rule - public final MockitoRule mockito = MockitoJUnit.rule(); - - @Mock - private CoexCoordinator.Callback mCallback; - @Mock - private CoexCoordinator.ErrorCallback mErrorCallback; - @Mock - private AuthenticationClient mFaceClient; - @Mock - private AuthenticationClient mFingerprintClient; - @Mock(extraInterfaces = {Udfps.class}) - private AuthenticationClient mUdfpsClient; - - private CoexCoordinator mCoexCoordinator; - - @Before - public void setUp() { - mCoexCoordinator = CoexCoordinator.getInstance(); - mCoexCoordinator.setAdvancedLogicEnabled(true); - mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true); - mCoexCoordinator.reset(); - } - - @Test - public void testBiometricPrompt_authSuccess() { - when(mFaceClient.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, - mFaceClient, mCallback); - verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testBiometricPrompt_authReject_whenNotLockedOut() { - when(mFaceClient.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback); - verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testBiometricPrompt_authReject_whenLockedOut() { - when(mFaceClient.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - mFaceClient, LockoutTracker.LOCKOUT_TIMED, mCallback); - verify(mCallback).sendHapticFeedback(); - verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testBiometricPrompt_coex_success() { - testBiometricPrompt_coex_success(false /* twice */); - } - - @Test - public void testBiometricPrompt_coex_successWithoutDouble() { - testBiometricPrompt_coex_success(true /* twice */); - } - - private void testBiometricPrompt_coex_success(boolean twice) { - initFaceAndFingerprintForBiometricPrompt(); - when(mFaceClient.wasAuthSuccessful()).thenReturn(true); - when(mUdfpsClient.wasAuthSuccessful()).thenReturn(twice, true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, - mFaceClient, mCallback); - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, - mUdfpsClient, mCallback); - - if (twice) { - verify(mCallback, never()).sendHapticFeedback(); - } else { - verify(mCallback).sendHapticFeedback(); - } - } - - @Test - public void testBiometricPrompt_coex_reject() { - initFaceAndFingerprintForBiometricPrompt(); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback); - - verify(mCallback, never()).sendHapticFeedback(); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback); - - verify(mCallback).sendHapticFeedback(); - } - - @Test - public void testBiometricPrompt_coex_errorNoHaptics() { - initFaceAndFingerprintForBiometricPrompt(); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationError(mFaceClient, - BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); - mCoexCoordinator.onAuthenticationError(mUdfpsClient, - BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); - - verify(mErrorCallback, never()).sendHapticFeedback(); - } - - private void initFaceAndFingerprintForBiometricPrompt() { - when(mFaceClient.isKeyguard()).thenReturn(false); - when(mFaceClient.isBiometricPrompt()).thenReturn(true); - when(mFaceClient.wasAuthAttempted()).thenReturn(true); - when(mUdfpsClient.isKeyguard()).thenReturn(false); - when(mUdfpsClient.isBiometricPrompt()).thenReturn(true); - when(mUdfpsClient.wasAuthAttempted()).thenReturn(true); - } - - @Test - public void testKeyguard_faceAuthOnly_success() { - when(mFaceClient.isKeyguard()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, - mFaceClient, mCallback); - verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testKeyguard_faceAuth_udfpsNotTouching_faceSuccess() { - when(mFaceClient.isKeyguard()).thenReturn(true); - - when(mUdfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, - mFaceClient, mCallback); - // Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead - // of removed) to keep this context. - // verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testKeyguard_faceAuthSuccess_nonBypass_udfpsRunning_noHaptics() { - testKeyguard_bypass_haptics(false /* bypassEnabled */, - true /* faceAccepted */, - false /* shouldReceiveHaptics */); - } - - @Test - public void testKeyguard_faceAuthReject_nonBypass_udfpsRunning_noHaptics() { - testKeyguard_bypass_haptics(false /* bypassEnabled */, - false /* faceAccepted */, - false /* shouldReceiveHaptics */); - } - - @Test - public void testKeyguard_faceAuthSuccess_bypass_udfpsRunning_haptics() { - testKeyguard_bypass_haptics(true /* bypassEnabled */, - true /* faceAccepted */, - true /* shouldReceiveHaptics */); - } - - @Test - public void testKeyguard_faceAuthReject_bypass_udfpsRunning_haptics() { - testKeyguard_bypass_haptics(true /* bypassEnabled */, - false /* faceAccepted */, - true /* shouldReceiveHaptics */); - } - - private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted, - boolean shouldReceiveHaptics) { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); - when(mUdfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - if (faceAccepted) { - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient, - mCallback); - } else { - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, - LockoutTracker.LOCKOUT_NONE, mCallback); - } - - if (shouldReceiveHaptics) { - verify(mCallback).sendHapticFeedback(); - } else { - verify(mCallback, never()).sendHapticFeedback(); - } - - verify(mCallback).sendAuthenticationResult(eq(faceAccepted) /* addAuthTokenIfStrong */); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedWithinBounds() { - testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */, - 0 /* udfpsRejectedAfterMs */); - } - - @Test - public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedAfterBounds() { - testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */, - CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS + 1 /* udfpsRejectedAfterMs */); - } - - @Test - public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsAccepted() { - testKeyguard_faceAuth_udfpsTouching_faceSuccess(true /* thenUdfpsAccepted */, - 0 /* udfpsRejectedAfterMs */); - } - - private void testKeyguard_faceAuth_udfpsTouching_faceSuccess(boolean thenUdfpsAccepted, - long udfpsRejectedAfterMs) { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mUdfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); - when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - // For easier reading - final CoexCoordinator.Callback faceCallback = mCallback; - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient, - faceCallback); - verify(faceCallback, never()).sendHapticFeedback(); - verify(faceCallback, never()).sendAuthenticationResult(anyBoolean()); - // CoexCoordinator requests the system to hold onto this AuthenticationClient until - // UDFPS result is known - verify(faceCallback, never()).handleLifecycleAfterAuth(); - - // Reset the mock - CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); - assertEquals(1, mCoexCoordinator.mSuccessfulAuths.size()); - assertEquals(mFaceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient); - if (thenUdfpsAccepted) { - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient, - udfpsCallback); - verify(udfpsCallback).sendHapticFeedback(); - verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */); - verify(udfpsCallback).handleLifecycleAfterAuth(); - - verify(faceCallback).sendAuthenticationCanceled(); - - assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); - } else { - mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, mUdfpsClient, - LockoutTracker.LOCKOUT_NONE, udfpsCallback); - if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) { - verify(udfpsCallback, never()).sendHapticFeedback(); - - verify(faceCallback).sendHapticFeedback(); - verify(faceCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); - verify(faceCallback).handleLifecycleAfterAuth(); - - assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); - } else { - assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); - - verify(faceCallback, never()).sendHapticFeedback(); - verify(faceCallback, never()).sendAuthenticationResult(anyBoolean()); - - verify(udfpsCallback).sendHapticFeedback(); - verify(udfpsCallback) - .sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); - verify(udfpsCallback).handleLifecycleAfterAuth(); - } - } - } - - @Test - public void testKeyguard_udfpsAuthSuccess_whileFaceScanning() { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mUdfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient, - mCallback); - verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(true)); - verify(mFaceClient).cancel(); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mUdfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, - LockoutTracker.LOCKOUT_NONE, mCallback); - verify(mCallback, never()).sendHapticFeedback(); - verify(mCallback).handleLifecycleAfterAuth(); - - // BiometricScheduler removes the face authentication client after rejection - mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - - // Then UDFPS rejected - CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); - mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mUdfpsClient, - LockoutTracker.LOCKOUT_NONE, udfpsCallback); - verify(udfpsCallback).sendHapticFeedback(); - verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); - verify(mCallback, never()).sendHapticFeedback(); - } - - @Test - public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case - when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mUdfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback); - // Auth was attempted - when(mUdfpsClient.getState()) - .thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED); - verify(mCallback).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 */, mFaceClient, - LockoutTracker.LOCKOUT_NONE, faceCallback); - verify(faceCallback).sendHapticFeedback(); - verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); - verify(mCallback).sendHapticFeedback(); - } - - @Test - public void testKeyguard_capacitiveAccepted_whenFaceScanning() { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mFingerprintClient.isKeyguard()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, - mFingerprintClient, mCallback); - verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testKeyguard_capacitiveRejected_whenFaceScanning() { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(mFingerprintClient.isKeyguard()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - mFingerprintClient, LockoutTracker.LOCKOUT_NONE, mCallback); - verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testNonKeyguard_rejectAndNotLockedOut() { - when(mFaceClient.isKeyguard()).thenReturn(false); - when(mFaceClient.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, - LockoutTracker.LOCKOUT_NONE, mCallback); - - verify(mCallback).sendHapticFeedback(); - verify(mCallback).sendAuthenticationResult(eq(false)); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testNonKeyguard_rejectLockedOut() { - when(mFaceClient.isKeyguard()).thenReturn(false); - when(mFaceClient.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, - LockoutTracker.LOCKOUT_TIMED, mCallback); - - verify(mCallback).sendHapticFeedback(); - verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); - verify(mCallback).handleLifecycleAfterAuth(); - } - - @Test - public void testCleanupRunnable() { - LinkedList successfulAuths = mock(LinkedList.class); - CoexCoordinator.SuccessfulAuth auth = mock(CoexCoordinator.SuccessfulAuth.class); - CoexCoordinator.Callback callback = mock(CoexCoordinator.Callback.class); - CoexCoordinator.SuccessfulAuth.CleanupRunnable runnable = - new CoexCoordinator.SuccessfulAuth.CleanupRunnable(successfulAuths, auth, callback); - runnable.run(); - - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - - verify(callback).handleLifecycleAfterAuth(); - verify(successfulAuths).remove(eq(auth)); - } - - @Test - public void testBiometricPrompt_FaceError() { - when(mFaceClient.isBiometricPrompt()).thenReturn(true); - when(mFaceClient.wasAuthAttempted()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - - mCoexCoordinator.onAuthenticationError(mFaceClient, - 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) { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); - when(mFaceClient.wasAuthAttempted()).thenReturn(true); - when(mFaceClient.wasUserDetected()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - - mCoexCoordinator.onAuthenticationError(mFaceClient, - 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) { - when(mFaceClient.isKeyguard()).thenReturn(true); - when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); - when(mFaceClient.wasAuthAttempted()).thenReturn(true); - when(mFaceClient.wasUserDetected()).thenReturn(true); - when(mUdfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - - mCoexCoordinator.onAuthenticationError(mFaceClient, - BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); - - if (bypassEnabled) { - verify(mErrorCallback).sendHapticFeedback(); - } else { - verify(mErrorCallback, never()).sendHapticFeedback(); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java index 0df3028805d2f..0815fe52e2626 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java @@ -118,8 +118,7 @@ public class UserAwareBiometricSchedulerTest { TEST_SENSOR_ID, mBiometricLogger, mBiometricContext, mUserStartedCallback, mStartOperationsFinish); } - }, - CoexCoordinator.getInstance()); + }); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index b60324e88f155..518946aa761a0 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -35,7 +35,6 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricScheduler; -import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -91,8 +90,7 @@ public class SensorTest { null /* gestureAvailabilityDispatcher */, mBiometricService, () -> USER_ID, - mUserSwitchCallback, - CoexCoordinator.getInstance()); + mUserSwitchCallback); mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), TAG, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index e1a4a2d9f969f..ff636c840bad4 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -35,7 +35,6 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricScheduler; -import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -91,8 +90,7 @@ public class SensorTest { null /* gestureAvailabilityDispatcher */, mBiometricService, () -> USER_ID, - mUserSwitchCallback, - CoexCoordinator.getInstance()); + mUserSwitchCallback); mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), TAG, mScheduler, SENSOR_ID, USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);