Merge "7/n: Add mechanism to hold onto already-completed operations" into sc-dev
This commit is contained in:
@@ -3365,6 +3365,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
|||||||
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
|
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
|
||||||
pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
|
pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
|
||||||
pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut);
|
pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut);
|
||||||
|
pw.println(" mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent);
|
||||||
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
|
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
|
||||||
if (isUdfpsEnrolled()) {
|
if (isUdfpsEnrolled()) {
|
||||||
pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true));
|
pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true));
|
||||||
@@ -3386,8 +3387,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
|||||||
+ getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
|
+ getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
|
||||||
pw.println(" disabled(DPM)=" + isFaceDisabled(userId));
|
pw.println(" disabled(DPM)=" + isFaceDisabled(userId));
|
||||||
pw.println(" possible=" + isUnlockWithFacePossible(userId));
|
pw.println(" possible=" + isUnlockWithFacePossible(userId));
|
||||||
|
pw.println(" listening: actual=" + mFaceRunningState
|
||||||
|
+ " expected=(" + (shouldListenForFace() ? 1 : 0));
|
||||||
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
|
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
|
||||||
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
|
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
|
||||||
|
pw.println(" mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
|
||||||
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
|
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
|
||||||
pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched);
|
pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server.biometrics.sensors;
|
package com.android.server.biometrics.sensors;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
@@ -30,6 +31,7 @@ import android.hardware.biometrics.BiometricManager;
|
|||||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.security.KeyStore;
|
import android.security.KeyStore;
|
||||||
import android.util.EventLog;
|
import android.util.EventLog;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
@@ -48,6 +50,18 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
|
|
||||||
private static final String TAG = "Biometrics/AuthenticationClient";
|
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 mIsStrongBiometric;
|
||||||
private final boolean mRequireConfirmation;
|
private final boolean mRequireConfirmation;
|
||||||
private final ActivityTaskManager mActivityTaskManager;
|
private final ActivityTaskManager mActivityTaskManager;
|
||||||
@@ -63,6 +77,20 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
|
|
||||||
protected boolean mAuthAttempted;
|
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,
|
public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
|
||||||
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
|
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
|
||||||
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
|
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
|
||||||
@@ -221,7 +249,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final CoexCoordinator coordinator = CoexCoordinator.getInstance();
|
final CoexCoordinator coordinator = CoexCoordinator.getInstance();
|
||||||
coordinator.onAuthenticationSucceeded(this, new CoexCoordinator.Callback() {
|
coordinator.onAuthenticationSucceeded(SystemClock.uptimeMillis(), this,
|
||||||
|
new CoexCoordinator.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
|
public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
|
||||||
if (addAuthTokenIfStrong && mIsStrongBiometric) {
|
if (addAuthTokenIfStrong && mIsStrongBiometric) {
|
||||||
@@ -262,6 +291,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
vibrateSuccess();
|
vibrateSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleLifecycleAfterAuth() {
|
||||||
|
AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Allow system-defined limit of number of attempts before giving up
|
// Allow system-defined limit of number of attempts before giving up
|
||||||
@@ -272,7 +306,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final CoexCoordinator coordinator = CoexCoordinator.getInstance();
|
final CoexCoordinator coordinator = CoexCoordinator.getInstance();
|
||||||
coordinator.onAuthenticationRejected(this, lockoutMode,
|
coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode,
|
||||||
new CoexCoordinator.Callback() {
|
new CoexCoordinator.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
|
public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
|
||||||
@@ -291,6 +325,11 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
vibrateError();
|
vibrateError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleLifecycleAfterAuth() {
|
||||||
|
AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
* Start authentication
|
||||||
*/
|
*/
|
||||||
@@ -345,6 +390,10 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @State int getState() {
|
||||||
|
return mState;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProtoEnum() {
|
public int getProtoEnum() {
|
||||||
return BiometricsProto.CM_AUTHENTICATE;
|
return BiometricsProto.CM_AUTHENTICATE;
|
||||||
|
|||||||
@@ -355,7 +355,6 @@ public class BiometricScheduler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new scheduler.
|
* Creates a new scheduler.
|
||||||
* @param context system_server context.
|
|
||||||
* @param tag for the specific instance of the scheduler. Should be unique.
|
* @param tag for the specific instance of the scheduler. Should be unique.
|
||||||
* @param sensorType the sensorType that this scheduler is handling.
|
* @param sensorType the sensorType that this scheduler is handling.
|
||||||
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
|
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
|
||||||
|
|||||||
@@ -22,12 +22,17 @@ import static com.android.server.biometrics.sensors.BiometricScheduler.sensorTyp
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.server.biometrics.sensors.BiometricScheduler.SensorType;
|
||||||
import com.android.server.biometrics.sensors.fingerprint.Udfps;
|
import com.android.server.biometrics.sensors.fingerprint.Udfps;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +48,9 @@ public class CoexCoordinator {
|
|||||||
"com.android.server.biometrics.sensors.CoexCoordinator.enable";
|
"com.android.server.biometrics.sensors.CoexCoordinator.enable";
|
||||||
private static final boolean DEBUG = true;
|
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
|
* Callback interface notifying the owner of "results" from the CoexCoordinator's business
|
||||||
* logic.
|
* logic.
|
||||||
@@ -58,10 +66,69 @@ public class CoexCoordinator {
|
|||||||
* Requests the owner to initiate a vibration for this event.
|
* Requests the owner to initiate a vibration for this event.
|
||||||
*/
|
*/
|
||||||
void sendHapticFeedback();
|
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;
|
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.
|
* @return a singleton instance.
|
||||||
*/
|
*/
|
||||||
@@ -85,11 +152,15 @@ public class CoexCoordinator {
|
|||||||
|
|
||||||
// SensorType to AuthenticationClient map
|
// SensorType to AuthenticationClient map
|
||||||
private final Map<Integer, AuthenticationClient<?>> mClientMap;
|
private final Map<Integer, AuthenticationClient<?>> mClientMap;
|
||||||
|
@VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths;
|
||||||
private boolean mAdvancedLogicEnabled;
|
private boolean mAdvancedLogicEnabled;
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
private CoexCoordinator() {
|
private CoexCoordinator() {
|
||||||
// Singleton
|
// Singleton
|
||||||
mClientMap = new HashMap<>();
|
mClientMap = new HashMap<>();
|
||||||
|
mSuccessfulAuths = new LinkedList<>();
|
||||||
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
|
public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType,
|
||||||
@@ -121,34 +192,43 @@ public class CoexCoordinator {
|
|||||||
mClientMap.remove(sensorType);
|
mClientMap.remove(sensorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAuthenticationSucceeded(@NonNull AuthenticationClient<?> client,
|
public void onAuthenticationSucceeded(long currentTimeMillis,
|
||||||
|
@NonNull AuthenticationClient<?> client,
|
||||||
@NonNull Callback callback) {
|
@NonNull Callback callback) {
|
||||||
if (client.isBiometricPrompt()) {
|
if (client.isBiometricPrompt()) {
|
||||||
callback.sendHapticFeedback();
|
callback.sendHapticFeedback();
|
||||||
// For BP, BiometricService will add the authToken to Keystore.
|
// For BP, BiometricService will add the authToken to Keystore.
|
||||||
callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
|
callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
|
||||||
|
callback.handleLifecycleAfterAuth();
|
||||||
} else if (isUnknownClient(client)) {
|
} else if (isUnknownClient(client)) {
|
||||||
// Client doesn't exist in our map for some reason. Give the user feedback so the
|
// 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
|
// device doesn't feel like it's stuck. All other cases below can assume that the
|
||||||
// client exists in our map.
|
// client exists in our map.
|
||||||
callback.sendHapticFeedback();
|
callback.sendHapticFeedback();
|
||||||
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
||||||
|
callback.handleLifecycleAfterAuth();
|
||||||
} else if (mAdvancedLogicEnabled && client.isKeyguard()) {
|
} else if (mAdvancedLogicEnabled && client.isKeyguard()) {
|
||||||
if (isSingleAuthOnly(client)) {
|
if (isSingleAuthOnly(client)) {
|
||||||
// Single sensor authentication
|
// Single sensor authentication
|
||||||
callback.sendHapticFeedback();
|
callback.sendHapticFeedback();
|
||||||
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
||||||
|
callback.handleLifecycleAfterAuth();
|
||||||
} else {
|
} else {
|
||||||
// Multi sensor authentication
|
// Multi sensor authentication
|
||||||
AuthenticationClient<?> udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null);
|
AuthenticationClient<?> udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null);
|
||||||
AuthenticationClient<?> face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
|
AuthenticationClient<?> face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
|
||||||
if (isCurrentFaceAuth(client)) {
|
if (isCurrentFaceAuth(client)) {
|
||||||
if (isPointerDown(udfps)) {
|
if (isUdfpsActivelyAuthing(udfps)) {
|
||||||
// Face auth success while UDFPS pointer down. No callback, no haptic.
|
// Face auth success while UDFPS is actively authing. No callback, no haptic
|
||||||
// Feedback will be provided after UDFPS result.
|
// 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 {
|
} else {
|
||||||
callback.sendHapticFeedback();
|
callback.sendHapticFeedback();
|
||||||
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
||||||
|
callback.handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
} else if (isCurrentUdfps(client)) {
|
} else if (isCurrentUdfps(client)) {
|
||||||
if (isFaceScanning()) {
|
if (isFaceScanning()) {
|
||||||
@@ -156,8 +236,12 @@ public class CoexCoordinator {
|
|||||||
// Cancel face auth and/or prevent it from invoking haptics/callbacks after
|
// Cancel face auth and/or prevent it from invoking haptics/callbacks after
|
||||||
face.cancel();
|
face.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeAndFinishAllFaceFromQueue();
|
||||||
|
|
||||||
callback.sendHapticFeedback();
|
callback.sendHapticFeedback();
|
||||||
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
||||||
|
callback.handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -165,13 +249,68 @@ public class CoexCoordinator {
|
|||||||
// FingerprintManager for highlighting fingers
|
// FingerprintManager for highlighting fingers
|
||||||
callback.sendHapticFeedback();
|
callback.sendHapticFeedback();
|
||||||
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
||||||
|
callback.handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAuthenticationRejected(@NonNull AuthenticationClient<?> client,
|
public void onAuthenticationRejected(long currentTimeMillis,
|
||||||
|
@NonNull AuthenticationClient<?> client,
|
||||||
@LockoutTracker.LockoutMode int lockoutMode,
|
@LockoutTracker.LockoutMode int lockoutMode,
|
||||||
@NonNull Callback callback) {
|
@NonNull Callback callback) {
|
||||||
callback.sendHapticFeedback();
|
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) {
|
if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
|
||||||
// Don't send onAuthenticationFailed if we're in lockout, it causes a
|
// Don't send onAuthenticationFailed if we're in lockout, it causes a
|
||||||
// janky UI on Keyguard/BiometricPrompt since "authentication failed"
|
// janky UI on Keyguard/BiometricPrompt since "authentication failed"
|
||||||
@@ -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) {
|
private boolean isCurrentFaceAuth(@NonNull AuthenticationClient<?> client) {
|
||||||
return client == mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
|
return client == mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
|
||||||
}
|
}
|
||||||
@@ -189,12 +352,13 @@ public class CoexCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFaceScanning() {
|
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) {
|
if (client instanceof Udfps) {
|
||||||
return ((Udfps) client).isPointerDown();
|
return client.getState() == AuthenticationClient.STATE_STARTED;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -221,7 +385,15 @@ public class CoexCoordinator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
|
|||||||
R.array.config_face_acquire_vendor_keyguard_ignorelist);
|
R.array.config_face_acquire_vendor_keyguard_ignorelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(@NonNull Callback callback) {
|
||||||
|
super.start(callback);
|
||||||
|
mState = STATE_STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected Callback wrapCallbackForStart(@NonNull Callback callback) {
|
protected Callback wrapCallbackForStart(@NonNull Callback callback) {
|
||||||
@@ -127,11 +133,21 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
|
|||||||
&& mLastAcquire != FaceManager.FACE_ACQUIRED_UNKNOWN;
|
&& 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
|
@Override
|
||||||
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
|
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
|
||||||
boolean authenticated, ArrayList<Byte> token) {
|
boolean authenticated, ArrayList<Byte> token) {
|
||||||
super.onAuthenticated(identifier, authenticated, token);
|
super.onAuthenticated(identifier, authenticated, token);
|
||||||
|
|
||||||
|
mState = STATE_STOPPED;
|
||||||
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
|
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
|
||||||
getStartTimeMs(),
|
getStartTimeMs(),
|
||||||
System.currentTimeMillis() - getStartTimeMs() /* latency */,
|
System.currentTimeMillis() - getStartTimeMs() /* latency */,
|
||||||
@@ -139,12 +155,6 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
|
|||||||
0 /* error */,
|
0 /* error */,
|
||||||
0 /* vendorError */,
|
0 /* vendorError */,
|
||||||
getTargetUserId()));
|
getTargetUserId()));
|
||||||
|
|
||||||
// For face, the authentication lifecycle ends either when
|
|
||||||
// 1) Authenticated == true
|
|
||||||
// 2) Error occurred
|
|
||||||
// 3) Authenticated == false
|
|
||||||
mCallback.onClientFinished(this, true /* success */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
|
|||||||
R.array.config_face_acquire_vendor_keyguard_ignorelist);
|
R.array.config_face_acquire_vendor_keyguard_ignorelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(@NonNull Callback callback) {
|
||||||
|
super.start(callback);
|
||||||
|
mState = STATE_STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected Callback wrapCallbackForStart(@NonNull Callback callback) {
|
protected Callback wrapCallbackForStart(@NonNull Callback callback) {
|
||||||
@@ -114,11 +120,21 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
|
|||||||
&& mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
|
&& mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
|
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
|
||||||
boolean authenticated, ArrayList<Byte> token) {
|
boolean authenticated, ArrayList<Byte> token) {
|
||||||
super.onAuthenticated(identifier, authenticated, token);
|
super.onAuthenticated(identifier, authenticated, token);
|
||||||
|
|
||||||
|
mState = STATE_STOPPED;
|
||||||
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
|
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
|
||||||
getStartTimeMs(),
|
getStartTimeMs(),
|
||||||
System.currentTimeMillis() - getStartTimeMs() /* latency */,
|
System.currentTimeMillis() - getStartTimeMs() /* latency */,
|
||||||
@@ -126,12 +142,6 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
|
|||||||
0 /* error */,
|
0 /* error */,
|
||||||
0 /* vendorError */,
|
0 /* vendorError */,
|
||||||
getTargetUserId()));
|
getTargetUserId()));
|
||||||
|
|
||||||
// For face, the authentication lifecycle ends either when
|
|
||||||
// 1) Authenticated == true
|
|
||||||
// 2) Error occurred
|
|
||||||
// 3) Authenticated == false
|
|
||||||
mCallback.onClientFinished(this, true /* success */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
|
|||||||
|
|
||||||
@NonNull private final LockoutCache mLockoutCache;
|
@NonNull private final LockoutCache mLockoutCache;
|
||||||
@Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
|
@Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
|
||||||
|
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
|
||||||
|
|
||||||
@Nullable private ICancellationSignal mCancellationSignal;
|
@Nullable private ICancellationSignal mCancellationSignal;
|
||||||
private boolean mIsPointerDown;
|
private boolean mIsPointerDown;
|
||||||
|
|
||||||
@@ -72,6 +74,19 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
|
|||||||
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */);
|
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */);
|
||||||
mLockoutCache = lockoutCache;
|
mLockoutCache = lockoutCache;
|
||||||
mUdfpsOverlayController = udfpsOverlayController;
|
mUdfpsOverlayController = udfpsOverlayController;
|
||||||
|
mSensorProps = sensorProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(@NonNull Callback callback) {
|
||||||
|
super.start(callback);
|
||||||
|
|
||||||
|
if (mSensorProps.isAnyUdfpsType()) {
|
||||||
|
// UDFPS requires user to touch before becoming "active"
|
||||||
|
mState = STATE_STARTED_PAUSED;
|
||||||
|
} else {
|
||||||
|
mState = STATE_STARTED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -80,14 +95,23 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
|
|||||||
return new CompositeCallback(createALSCallback(), callback);
|
return new CompositeCallback(createALSCallback(), callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleLifecycleAfterAuth(boolean authenticated) {
|
||||||
|
if (authenticated) {
|
||||||
|
mCallback.onClientFinished(this, true /* success */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
|
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
|
||||||
boolean authenticated, ArrayList<Byte> token) {
|
boolean authenticated, ArrayList<Byte> token) {
|
||||||
super.onAuthenticated(identifier, authenticated, token);
|
super.onAuthenticated(identifier, authenticated, token);
|
||||||
|
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
|
mState = STATE_STOPPED;
|
||||||
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
|
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
|
||||||
mCallback.onClientFinished(this, true /* success */);
|
} else {
|
||||||
|
mState = STATE_STARTED_PAUSED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +169,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
|
|||||||
public void onPointerDown(int x, int y, float minor, float major) {
|
public void onPointerDown(int x, int y, float minor, float major) {
|
||||||
try {
|
try {
|
||||||
mIsPointerDown = true;
|
mIsPointerDown = true;
|
||||||
|
mState = STATE_STARTED;
|
||||||
getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
|
getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
|
||||||
if (getListener() != null) {
|
if (getListener() != null) {
|
||||||
getListener().onUdfpsPointerDown(getSensorId());
|
getListener().onUdfpsPointerDown(getSensorId());
|
||||||
@@ -158,6 +183,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
|
|||||||
public void onPointerUp() {
|
public void onPointerUp() {
|
||||||
try {
|
try {
|
||||||
mIsPointerDown = false;
|
mIsPointerDown = false;
|
||||||
|
mState = STATE_STARTED_PAUSED;
|
||||||
getFreshDaemon().onPointerUp(0 /* pointerId */);
|
getFreshDaemon().onPointerUp(0 /* pointerId */);
|
||||||
if (getListener() != null) {
|
if (getListener() != null) {
|
||||||
getListener().onUdfpsPointerUp(getSensorId());
|
getListener().onUdfpsPointerUp(getSensorId());
|
||||||
|
|||||||
@@ -622,7 +622,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
|
|||||||
opPackageName, cookie, false /* requireConfirmation */,
|
opPackageName, cookie, false /* requireConfirmation */,
|
||||||
mSensorProperties.sensorId, isStrongBiometric, statsClient,
|
mSensorProperties.sensorId, isStrongBiometric, statsClient,
|
||||||
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
|
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
|
||||||
allowBackgroundAuthentication);
|
allowBackgroundAuthentication, mSensorProperties);
|
||||||
mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
|
mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.hardware.biometrics.BiometricConstants;
|
|||||||
import android.hardware.biometrics.BiometricFingerprintConstants;
|
import android.hardware.biometrics.BiometricFingerprintConstants;
|
||||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||||
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
|
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||||
import android.hardware.fingerprint.IUdfpsOverlayController;
|
import android.hardware.fingerprint.IUdfpsOverlayController;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
@@ -52,6 +53,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
|
|||||||
|
|
||||||
private final LockoutFrameworkImpl mLockoutFrameworkImpl;
|
private final LockoutFrameworkImpl mLockoutFrameworkImpl;
|
||||||
@Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
|
@Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
|
||||||
|
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
|
||||||
|
|
||||||
private boolean mIsPointerDown;
|
private boolean mIsPointerDown;
|
||||||
|
|
||||||
FingerprintAuthenticationClient(@NonNull Context context,
|
FingerprintAuthenticationClient(@NonNull Context context,
|
||||||
@@ -62,13 +65,27 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
|
|||||||
@NonNull TaskStackListener taskStackListener,
|
@NonNull TaskStackListener taskStackListener,
|
||||||
@NonNull LockoutFrameworkImpl lockoutTracker,
|
@NonNull LockoutFrameworkImpl lockoutTracker,
|
||||||
@Nullable IUdfpsOverlayController udfpsOverlayController,
|
@Nullable IUdfpsOverlayController udfpsOverlayController,
|
||||||
boolean allowBackgroundAuthentication) {
|
boolean allowBackgroundAuthentication,
|
||||||
|
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
|
||||||
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
|
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
|
||||||
owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
|
owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
|
||||||
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
|
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
|
||||||
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */);
|
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */);
|
||||||
mLockoutFrameworkImpl = lockoutTracker;
|
mLockoutFrameworkImpl = lockoutTracker;
|
||||||
mUdfpsOverlayController = udfpsOverlayController;
|
mUdfpsOverlayController = udfpsOverlayController;
|
||||||
|
mSensorProps = sensorProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(@NonNull Callback callback) {
|
||||||
|
super.start(callback);
|
||||||
|
|
||||||
|
if (mSensorProps.isAnyUdfpsType()) {
|
||||||
|
// UDFPS requires user to touch before becoming "active"
|
||||||
|
mState = STATE_STARTED_PAUSED;
|
||||||
|
} else {
|
||||||
|
mState = STATE_STARTED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -88,10 +105,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
|
|||||||
// Note that authentication doesn't end when Authenticated == false
|
// Note that authentication doesn't end when Authenticated == false
|
||||||
|
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
|
mState = STATE_STOPPED;
|
||||||
resetFailedAttempts(getTargetUserId());
|
resetFailedAttempts(getTargetUserId());
|
||||||
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
|
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
|
||||||
mCallback.onClientFinished(this, true /* success */);
|
|
||||||
} else {
|
} else {
|
||||||
|
mState = STATE_STARTED_PAUSED;
|
||||||
final @LockoutTracker.LockoutMode int lockoutMode =
|
final @LockoutTracker.LockoutMode int lockoutMode =
|
||||||
mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
|
mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
|
||||||
if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
|
if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
|
||||||
@@ -124,6 +142,13 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
|
|||||||
mLockoutFrameworkImpl.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
|
mLockoutFrameworkImpl.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleLifecycleAfterAuth(boolean authenticated) {
|
||||||
|
if (authenticated) {
|
||||||
|
mCallback.onClientFinished(this, true /* success */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
|
public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
|
||||||
mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
|
mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
|
||||||
@@ -162,6 +187,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
|
|||||||
@Override
|
@Override
|
||||||
public void onPointerDown(int x, int y, float minor, float major) {
|
public void onPointerDown(int x, int y, float minor, float major) {
|
||||||
mIsPointerDown = true;
|
mIsPointerDown = true;
|
||||||
|
mState = STATE_STARTED;
|
||||||
UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
|
UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
|
||||||
if (getListener() != null) {
|
if (getListener() != null) {
|
||||||
try {
|
try {
|
||||||
@@ -175,6 +201,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
|
|||||||
@Override
|
@Override
|
||||||
public void onPointerUp() {
|
public void onPointerUp() {
|
||||||
mIsPointerDown = false;
|
mIsPointerDown = false;
|
||||||
|
mState = STATE_STARTED_PAUSED;
|
||||||
UdfpsHelper.onFingerUp(getFreshDaemon());
|
UdfpsHelper.onFingerUp(getFreshDaemon());
|
||||||
if (getListener() != null) {
|
if (getListener() != null) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -372,6 +372,11 @@ public class BiometricSchedulerTest {
|
|||||||
protected void startHalOperation() {
|
protected void startHalOperation() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleLifecycleAfterAuth(boolean authenticated) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
|
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
|
||||||
@@ -395,6 +400,11 @@ public class BiometricSchedulerTest {
|
|||||||
protected void startHalOperation() {
|
protected void startHalOperation() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleLifecycleAfterAuth(boolean authenticated) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestClientMonitor2 extends TestClientMonitor {
|
private static class TestClientMonitor2 extends TestClientMonitor {
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ 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_FACE;
|
||||||
import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_UDFPS;
|
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.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -28,8 +31,11 @@ import static org.mockito.Mockito.when;
|
|||||||
import static org.mockito.Mockito.withSettings;
|
import static org.mockito.Mockito.withSettings;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
import com.android.server.biometrics.sensors.fingerprint.Udfps;
|
import com.android.server.biometrics.sensors.fingerprint.Udfps;
|
||||||
@@ -39,6 +45,8 @@ import org.junit.Test;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
@Presubmit
|
@Presubmit
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class CoexCoordinatorTest {
|
public class CoexCoordinatorTest {
|
||||||
@@ -46,6 +54,7 @@ public class CoexCoordinatorTest {
|
|||||||
private static final String TAG = "CoexCoordinatorTest";
|
private static final String TAG = "CoexCoordinatorTest";
|
||||||
|
|
||||||
private CoexCoordinator mCoexCoordinator;
|
private CoexCoordinator mCoexCoordinator;
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@@ -55,6 +64,9 @@ public class CoexCoordinatorTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
mCoexCoordinator = CoexCoordinator.getInstance();
|
mCoexCoordinator = CoexCoordinator.getInstance();
|
||||||
mCoexCoordinator.setAdvancedLogicEnabled(true);
|
mCoexCoordinator.setAdvancedLogicEnabled(true);
|
||||||
}
|
}
|
||||||
@@ -68,9 +80,10 @@ public class CoexCoordinatorTest {
|
|||||||
|
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
||||||
|
|
||||||
mCoexCoordinator.onAuthenticationSucceeded(client, mCallback);
|
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
|
||||||
verify(mCallback).sendHapticFeedback();
|
verify(mCallback).sendHapticFeedback();
|
||||||
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
|
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -82,9 +95,11 @@ public class CoexCoordinatorTest {
|
|||||||
|
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
||||||
|
|
||||||
mCoexCoordinator.onAuthenticationRejected(client, LockoutTracker.LOCKOUT_NONE, mCallback);
|
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
|
||||||
|
client, LockoutTracker.LOCKOUT_NONE, mCallback);
|
||||||
verify(mCallback).sendHapticFeedback();
|
verify(mCallback).sendHapticFeedback();
|
||||||
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
|
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -96,9 +111,11 @@ public class CoexCoordinatorTest {
|
|||||||
|
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
||||||
|
|
||||||
mCoexCoordinator.onAuthenticationRejected(client, LockoutTracker.LOCKOUT_TIMED, mCallback);
|
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */,
|
||||||
|
client, LockoutTracker.LOCKOUT_TIMED, mCallback);
|
||||||
verify(mCallback).sendHapticFeedback();
|
verify(mCallback).sendHapticFeedback();
|
||||||
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
|
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -110,9 +127,10 @@ public class CoexCoordinatorTest {
|
|||||||
|
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
|
||||||
|
|
||||||
mCoexCoordinator.onAuthenticationSucceeded(client, mCallback);
|
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback);
|
||||||
verify(mCallback).sendHapticFeedback();
|
verify(mCallback).sendHapticFeedback();
|
||||||
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
|
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -130,13 +148,33 @@ public class CoexCoordinatorTest {
|
|||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
|
||||||
|
|
||||||
mCoexCoordinator.onAuthenticationSucceeded(faceClient, mCallback);
|
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
|
||||||
|
mCallback);
|
||||||
verify(mCallback).sendHapticFeedback();
|
verify(mCallback).sendHapticFeedback();
|
||||||
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
|
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeyguard_faceAuth_udfpsTouching_faceSuccess() {
|
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) {
|
||||||
mCoexCoordinator.reset();
|
mCoexCoordinator.reset();
|
||||||
|
|
||||||
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
|
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
|
||||||
@@ -146,13 +184,54 @@ public class CoexCoordinatorTest {
|
|||||||
withSettings().extraInterfaces(Udfps.class));
|
withSettings().extraInterfaces(Udfps.class));
|
||||||
when(udfpsClient.isKeyguard()).thenReturn(true);
|
when(udfpsClient.isKeyguard()).thenReturn(true);
|
||||||
when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
|
when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
|
||||||
|
when (udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
|
||||||
|
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
|
||||||
|
|
||||||
mCoexCoordinator.onAuthenticationSucceeded(faceClient, mCallback);
|
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
|
||||||
|
mCallback);
|
||||||
verify(mCallback, never()).sendHapticFeedback();
|
verify(mCallback, never()).sendHapticFeedback();
|
||||||
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
|
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
|
||||||
|
// CoexCoordinator requests the system to hold onto this AuthenticationClient until
|
||||||
|
// UDFPS result is known
|
||||||
|
verify(mCallback, never()).handleLifecycleAfterAuth();
|
||||||
|
|
||||||
|
// Reset the mock
|
||||||
|
CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
|
||||||
|
assertEquals(1, mCoexCoordinator.mSuccessfulAuths.size());
|
||||||
|
assertEquals(faceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient);
|
||||||
|
if (thenUdfpsAccepted) {
|
||||||
|
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
|
||||||
|
udfpsCallback);
|
||||||
|
verify(udfpsCallback).sendHapticFeedback();
|
||||||
|
verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
||||||
|
verify(udfpsCallback).handleLifecycleAfterAuth();
|
||||||
|
|
||||||
|
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
|
||||||
|
} else {
|
||||||
|
mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient,
|
||||||
|
LockoutTracker.LOCKOUT_NONE, udfpsCallback);
|
||||||
|
if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
|
||||||
|
verify(udfpsCallback, never()).sendHapticFeedback();
|
||||||
|
|
||||||
|
verify(mCallback).sendHapticFeedback();
|
||||||
|
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
|
|
||||||
|
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
|
||||||
|
} else {
|
||||||
|
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
|
||||||
|
|
||||||
|
verify(mCallback, never()).sendHapticFeedback();
|
||||||
|
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
|
||||||
|
|
||||||
|
verify(udfpsCallback).sendHapticFeedback();
|
||||||
|
verify(udfpsCallback)
|
||||||
|
.sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
|
||||||
|
verify(udfpsCallback).handleLifecycleAfterAuth();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,6 +240,7 @@ public class CoexCoordinatorTest {
|
|||||||
|
|
||||||
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
|
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
|
||||||
when(faceClient.isKeyguard()).thenReturn(true);
|
when(faceClient.isKeyguard()).thenReturn(true);
|
||||||
|
when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
|
||||||
|
|
||||||
AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
|
AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
|
||||||
withSettings().extraInterfaces(Udfps.class));
|
withSettings().extraInterfaces(Udfps.class));
|
||||||
@@ -170,9 +250,60 @@ public class CoexCoordinatorTest {
|
|||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
||||||
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
|
||||||
|
|
||||||
mCoexCoordinator.onAuthenticationSucceeded(udfpsClient, mCallback);
|
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient,
|
||||||
|
mCallback);
|
||||||
verify(mCallback).sendHapticFeedback();
|
verify(mCallback).sendHapticFeedback();
|
||||||
verify(mCallback).sendAuthenticationResult(eq(true));
|
verify(mCallback).sendAuthenticationResult(eq(true));
|
||||||
verify(faceClient).cancel();
|
verify(faceClient).cancel();
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonKeyguard_rejectAndNotLockedOut() {
|
||||||
|
mCoexCoordinator.reset();
|
||||||
|
|
||||||
|
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
|
||||||
|
when(faceClient.isKeyguard()).thenReturn(false);
|
||||||
|
when(faceClient.isBiometricPrompt()).thenReturn(true);
|
||||||
|
|
||||||
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
||||||
|
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
|
||||||
|
LockoutTracker.LOCKOUT_NONE, mCallback);
|
||||||
|
|
||||||
|
verify(mCallback).sendHapticFeedback();
|
||||||
|
verify(mCallback).sendAuthenticationResult(eq(false));
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonKeyguard_rejectLockedOut() {
|
||||||
|
mCoexCoordinator.reset();
|
||||||
|
|
||||||
|
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
|
||||||
|
when(faceClient.isKeyguard()).thenReturn(false);
|
||||||
|
when(faceClient.isBiometricPrompt()).thenReturn(true);
|
||||||
|
|
||||||
|
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
|
||||||
|
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
|
||||||
|
LockoutTracker.LOCKOUT_TIMED, mCallback);
|
||||||
|
|
||||||
|
verify(mCallback).sendHapticFeedback();
|
||||||
|
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
|
||||||
|
verify(mCallback).handleLifecycleAfterAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCleanupRunnable() {
|
||||||
|
LinkedList<CoexCoordinator.SuccessfulAuth> 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user