summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/hardware/face/FaceManager.java81
-rw-r--r--core/java/android/hardware/face/FaceServiceReceiver.java13
-rw-r--r--core/java/android/hardware/face/IFaceServiceReceiver.aidl4
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java16
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java3
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java20
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java117
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java74
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java71
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java105
21 files changed, 338 insertions, 249 deletions
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 9a27a99437bc..55c90ce2a32f 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -69,8 +69,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
private static final int MSG_SET_FEATURE_COMPLETED = 107;
private static final int MSG_CHALLENGE_GENERATED = 108;
private static final int MSG_FACE_DETECTED = 109;
- private static final int MSG_CHALLENGE_INTERRUPTED = 110;
- private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;
private static final int MSG_AUTHENTICATION_FRAME = 112;
private static final int MSG_ENROLLMENT_FRAME = 113;
@@ -102,8 +100,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
@Override // binder call
public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
- face).sendToTarget();
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
+ isStrongBiometric ? 1 : 0, face).sendToTarget();
}
@Override // binder call
@@ -142,22 +140,12 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
@Override
- public void onChallengeGenerated(int sensorId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
+ mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
.sendToTarget();
}
@Override
- public void onChallengeInterrupted(int sensorId) {
- mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPTED, sensorId).sendToTarget();
- }
-
- @Override
- public void onChallengeInterruptFinished(int sensorId) {
- mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
- }
-
- @Override
public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
}
@@ -434,16 +422,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
*
* @see com.android.server.locksettings.LockSettingsService
*
- * TODO(b/171335732): should take userId
- *
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
- public void generateChallenge(int sensorId, GenerateChallengeCallback callback) {
+ public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) {
try {
mGenerateChallengeCallback = callback;
- mService.generateChallenge(mToken, sensorId, 0 /* userId */, mServiceReceiver,
+ mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -452,12 +438,13 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
- * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
- * enumerated sensor.
+ * Same as {@link #generateChallenge(int, int, GenerateChallengeCallback)}, but assumes the
+ * first enumerated sensor.
+ *
* @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
- public void generateChallenge(GenerateChallengeCallback callback) {
+ public void generateChallenge(int userId, GenerateChallengeCallback callback) {
final List<FaceSensorPropertiesInternal> faceSensorProperties =
getSensorPropertiesInternal();
if (faceSensorProperties.isEmpty()) {
@@ -466,7 +453,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
final int sensorId = faceSensorProperties.get(0).sensorId;
- generateChallenge(sensorId, callback);
+ generateChallenge(sensorId, userId, callback);
}
/**
@@ -1120,25 +1107,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
- * Callback structure provided to {@link #generateChallenge(int, GenerateChallengeCallback)}.
+ * Callback structure provided to {@link #generateChallenge(int, int,
+ * GenerateChallengeCallback)}.
+ *
* @hide
*/
public interface GenerateChallengeCallback {
/**
* Invoked when a challenge has been generated.
*/
- void onGenerateChallengeResult(int sensorId, long challenge);
-
- /**
- * Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
- * {@link #generateChallenge(int, GenerateChallengeCallback)}, but
- */
- default void onChallengeInterrupted(int sensorId) {}
-
- /**
- * Invoked when the interrupting client has finished (e.g. revoked its challenge).
- */
- default void onChallengeInterruptFinished(int sensorId) {}
+ void onGenerateChallengeResult(int sensorId, int userId, long challenge);
}
private class OnEnrollCancelListener implements OnCancelListener {
@@ -1212,18 +1190,13 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
args.recycle();
break;
case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
+ sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (long) msg.obj /* challenge */);
break;
case MSG_FACE_DETECTED:
sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
(boolean) msg.obj /* isStrongBiometric */);
break;
- case MSG_CHALLENGE_INTERRUPTED:
- sendChallengeInterrupted((int) msg.obj /* sensorId */);
- break;
- case MSG_CHALLENGE_INTERRUPT_FINISHED:
- sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
- break;
case MSG_AUTHENTICATION_FRAME:
sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
break;
@@ -1251,11 +1224,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
mGetFeatureCallback.onCompleted(success, features, featureState);
}
- private void sendChallengeGenerated(int sensorId, long challenge) {
+ private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
if (mGenerateChallengeCallback == null) {
return;
}
- mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, challenge);
+ mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
}
private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
@@ -1266,22 +1239,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
}
- private void sendChallengeInterrupted(int sensorId) {
- if (mGenerateChallengeCallback == null) {
- Slog.e(TAG, "sendChallengeInterrupted, callback null");
- return;
- }
- mGenerateChallengeCallback.onChallengeInterrupted(sensorId);
- }
-
- private void sendChallengeInterruptFinished(int sensorId) {
- if (mGenerateChallengeCallback == null) {
- Slog.e(TAG, "sendChallengeInterruptFinished, callback null");
- return;
- }
- mGenerateChallengeCallback.onChallengeInterruptFinished(sensorId);
- }
-
private void sendRemovedResult(Face face, int remaining) {
if (mRemovalCallback == null) {
return;
diff --git a/core/java/android/hardware/face/FaceServiceReceiver.java b/core/java/android/hardware/face/FaceServiceReceiver.java
index 9e62ca5e466b..9e7859277bd2 100644
--- a/core/java/android/hardware/face/FaceServiceReceiver.java
+++ b/core/java/android/hardware/face/FaceServiceReceiver.java
@@ -72,17 +72,8 @@ public class FaceServiceReceiver extends IFaceServiceReceiver.Stub {
}
@Override
- public void onChallengeGenerated(int sensorId, long challenge) throws RemoteException {
-
- }
-
- @Override
- public void onChallengeInterrupted(int sensorId) throws RemoteException {
-
- }
-
- @Override
- public void onChallengeInterruptFinished(int sensorId) throws RemoteException {
+ public void onChallengeGenerated(int sensorId, int userId, long challenge)
+ throws RemoteException {
}
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 0ccb39583554..c4d9bf26c3ea 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -33,9 +33,7 @@ oneway interface IFaceServiceReceiver {
void onRemoved(in Face face, int remaining);
void onFeatureSet(boolean success, int feature);
void onFeatureGet(boolean success, in int[] features, in boolean[] featureState);
- void onChallengeGenerated(int sensorId, long challenge);
- void onChallengeInterrupted(int sensorId);
- void onChallengeInterruptFinished(int sensorId);
+ void onChallengeGenerated(int sensorId, int userId, long challenge);
void onAuthenticationFrame(in FaceAuthenticationFrame frame);
void onEnrollmentFrame(in FaceEnrollFrame frame);
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index b52955d035b5..8aeb5cd8f428 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -475,10 +475,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
/**
+ * Callbacks for generate challenge operations.
+ *
* @hide
*/
public interface GenerateChallengeCallback {
- void onChallengeGenerated(int sensorId, long challenge);
+ /** Called when a challenged has been generated. */
+ void onChallengeGenerated(int sensorId, int userId, long challenge);
}
/**
@@ -1124,7 +1127,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
break;
case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, (long) msg.obj /* challenge */);
+ sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (long) msg.obj /* challenge */);
break;
case MSG_FINGERPRINT_DETECTED:
sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
@@ -1233,12 +1237,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
- private void sendChallengeGenerated(int sensorId, long challenge) {
+ private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
if (mGenerateChallengeCallback == null) {
Slog.e(TAG, "sendChallengeGenerated, callback null");
return;
}
- mGenerateChallengeCallback.onChallengeGenerated(sensorId, challenge);
+ mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
}
private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
@@ -1454,8 +1458,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
@Override // binder call
- public void onChallengeGenerated(int sensorId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, 0, challenge)
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
+ mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
.sendToTarget();
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
index 798e87beb52a..a9779b51321b 100644
--- a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
+++ b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
@@ -61,7 +61,8 @@ public class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub
}
@Override
- public void onChallengeGenerated(int sensorId, long challenge) throws RemoteException {
+ public void onChallengeGenerated(int sensorId, int userId, long challenge)
+ throws RemoteException {
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 1bd284d1ec05..9cea1fed629d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -29,7 +29,7 @@ oneway interface IFingerprintServiceReceiver {
void onAuthenticationFailed();
void onError(int error, int vendorCode);
void onRemoved(in Fingerprint fp, int remaining);
- void onChallengeGenerated(int sensorId, long challenge);
+ void onChallengeGenerated(int sensorId, int userId, long challenge);
void onUdfpsPointerDown(int sensorId);
void onUdfpsPointerUp(int sensorId);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 677ea5dc8f03..6482a2eead42 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -46,6 +46,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
* Interface that ClientMonitor holders should use to receive callbacks.
*/
public interface Callback {
+
/**
* Invoked when the ClientMonitor operation has been started (e.g. reached the head of
* the queue and becomes the current operation).
@@ -222,6 +223,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
+ this.getClass().getSimpleName()
+ ", " + getProtoEnum()
+ ", " + getOwnerString()
- + ", " + getCookie() + "}";
+ + ", " + getCookie()
+ + ", " + getTargetUserId() + "}";
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 62a9769aee1a..f1c786b4977c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -132,11 +132,13 @@ public class ClientMonitorCallbackConverter {
}
}
- public void onChallengeGenerated(int sensorId, long challenge) throws RemoteException {
+ /** Called when a challenged has been generated. */
+ public void onChallengeGenerated(int sensorId, int userId, long challenge)
+ throws RemoteException {
if (mFaceServiceReceiver != null) {
- mFaceServiceReceiver.onChallengeGenerated(sensorId, challenge);
+ mFaceServiceReceiver.onChallengeGenerated(sensorId, userId, challenge);
} else if (mFingerprintServiceReceiver != null) {
- mFingerprintServiceReceiver.onChallengeGenerated(sensorId, challenge);
+ mFingerprintServiceReceiver.onChallengeGenerated(sensorId, userId, challenge);
}
}
@@ -153,18 +155,6 @@ public class ClientMonitorCallbackConverter {
}
}
- public void onChallengeInterrupted(int sensorId) throws RemoteException {
- if (mFaceServiceReceiver != null) {
- mFaceServiceReceiver.onChallengeInterrupted(sensorId);
- }
- }
-
- public void onChallengeInterruptFinished(int sensorId) throws RemoteException {
- if (mFaceServiceReceiver != null) {
- mFaceServiceReceiver.onChallengeInterruptFinished(sensorId);
- }
- }
-
// Fingerprint-specific callbacks for FingerprintManager only
public void onUdfpsPointerDown(int sensorId) throws RemoteException {
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 1fcad62e3a07..3d74f369efde 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -40,7 +40,7 @@ public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> {
@Override
public void unableToStart() {
try {
- getListener().onChallengeGenerated(getSensorId(), 0L);
+ getListener().onChallengeGenerated(getSensorId(), getTargetUserId(), 0L);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 87269237bc85..57c1c74a51a8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -110,17 +110,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
}
@Override
- public void onChallengeGenerated(int sensorId, long challenge) {
-
- }
-
- @Override
- public void onChallengeInterrupted(int sensorId) {
-
- }
-
- @Override
- public void onChallengeInterruptFinished(int sensorId) {
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 904c39922a06..d76036bf432d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -52,7 +52,7 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISessio
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
- getListener().onChallengeGenerated(sensorId, challenge);
+ getListener().onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send challenge", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index f8067670f61f..d0580c712610 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -99,17 +99,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
}
@Override
- public void onChallengeGenerated(int sensorId, long challenge) {
-
- }
-
- @Override
- public void onChallengeInterrupted(int sensorId) {
-
- }
-
- @Override
- public void onChallengeInterruptFinished(int sensorId) {
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index f908fba8693c..a5bb0f430609 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -80,6 +80,7 @@ import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -93,7 +94,13 @@ import java.util.Map;
public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
private static final String TAG = "Face10";
+
private static final int ENROLL_TIMEOUT_SEC = 75;
+ private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
+ private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS =
+ FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC * 1000;
+ @VisibleForTesting
+ public static Clock sSystemClock = Clock.systemUTC();
private boolean mTestHalEnabled;
@@ -102,19 +109,15 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@NonNull private final BiometricScheduler mScheduler;
@NonNull private final Handler mHandler;
@NonNull private final HalClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
- @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final LockoutHalImpl mLockoutTracker;
@NonNull private final UsageStats mUsageStats;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
- // If a challenge is generated, keep track of its owner. Since IBiometricsFace@1.0 only
- // supports a single in-flight challenge, we must notify the interrupted owner that its
- // challenge is no longer valid. The interrupted owner will be notified when the interrupter
- // has finished.
- @Nullable private FaceGenerateChallengeClient mCurrentChallengeOwner;
private int mCurrentUserId = UserHandle.USER_NULL;
private final int mSensorId;
+ private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+ private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
@@ -335,7 +338,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mAuthenticatorIds = new HashMap<>();
mLazyDaemon = Face10.this::getDaemon;
mLockoutTracker = new LockoutHalImpl();
- mLockoutResetDispatcher = lockoutResetDispatcher;
mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
mScheduler, mLockoutTracker, lockoutResetDispatcher);
mHalResultController.setCallback(() -> {
@@ -480,56 +482,56 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
return getDaemon() != null;
}
+ private boolean isGeneratedChallengeCacheValid() {
+ return mGeneratedChallengeCache != null
+ && sSystemClock.millis() - mGeneratedChallengeCache.getCreatedAt()
+ < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+ }
+
+ private void incrementChallengeCount() {
+ mGeneratedChallengeCount.add(0, sSystemClock.millis());
+ }
+
+ private int decrementChallengeCount() {
+ final long now = sSystemClock.millis();
+ // ignore values that are old in case generate/revoke calls are not matched
+ // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+ mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+ if (!mGeneratedChallengeCount.isEmpty()) {
+ mGeneratedChallengeCount.remove(0);
+ }
+ return mGeneratedChallengeCount.size();
+ }
+
/**
- * {@link IBiometricsFace} only supports a single in-flight challenge. In cases where two
- * callers both need challenges (e.g. resetLockout right before enrollment), we need to ensure
- * that either:
- * 1) generateChallenge/operation/revokeChallenge is complete before the next generateChallenge
- * is processed by the scheduler, or
- * 2) the generateChallenge callback provides a mechanism for notifying the caller that its
- * challenge has been invalidated by a subsequent caller, as well as a mechanism for
- * notifying the previous caller that the interrupting operation is complete (e.g. the
- * interrupting client's challenge has been revoked, so that the interrupted client can
- * start retry logic if necessary). See
- * {@link
- *android.hardware.face.FaceManager.GenerateChallengeCallback#onChallengeInterruptFinished(int)}
- * The only case of conflicting challenges is currently resetLockout --> enroll. So, the second
- * option seems better as it prioritizes the new operation, which is user-facing.
+ * {@link IBiometricsFace} only supports a single in-flight challenge but there are cases where
+ * two callers both need challenges (e.g. resetLockout right before enrollment).
*/
@Override
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- if (mCurrentChallengeOwner != null) {
- final ClientMonitorCallbackConverter listener =
- mCurrentChallengeOwner.getListener();
- Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner
- + ", listener: " + listener
- + ", interrupted by: " + opPackageName);
- if (listener != null) {
- try {
- listener.onChallengeInterrupted(mSensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify challenge interrupted", e);
- }
- }
+ incrementChallengeCount();
+
+ if (isGeneratedChallengeCacheValid()) {
+ Slog.d(TAG, "Current challenge is cached and will be reused");
+ mGeneratedChallengeCache.reuseResult(receiver);
+ return;
}
scheduleUpdateActiveUserWithoutHandler(userId);
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, mCurrentChallengeOwner);
+ opPackageName, mSensorId, sSystemClock.millis());
+ mGeneratedChallengeCache = client;
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
if (client != clientMonitor) {
Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client."
+ " Expecting: " + client + ", received: " + clientMonitor);
- return;
}
- Slog.d(TAG, "Current challenge owner: " + client);
- mCurrentChallengeOwner = client;
}
});
});
@@ -539,14 +541,16 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- if (mCurrentChallengeOwner != null
- && !mCurrentChallengeOwner.getOwnerString().contentEquals(opPackageName)) {
- Slog.e(TAG, "scheduleRevokeChallenge, package: " + opPackageName
- + " attempting to revoke challenge owned by: "
- + mCurrentChallengeOwner.getOwnerString());
+ final boolean shouldRevoke = decrementChallengeCount() == 0;
+ if (!shouldRevoke) {
+ Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+ + mGeneratedChallengeCount);
return;
}
+ Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
+ mGeneratedChallengeCache = null;
+
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
mLazyDaemon, token, userId, opPackageName, mSensorId);
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@@ -556,33 +560,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
if (client != clientMonitor) {
Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
+ "Expecting: " + client + ", received: " + clientMonitor);
- return;
- }
-
- if (mCurrentChallengeOwner == null) {
- // Can happen if revoke is incorrectly called, for example without a
- // preceding generateChallenge
- Slog.w(TAG, "Current challenge owner is null");
- return;
- }
-
- final FaceGenerateChallengeClient previousChallengeOwner =
- mCurrentChallengeOwner.getInterruptedClient();
- mCurrentChallengeOwner = null;
-
- Slog.d(TAG, "Previous challenge owner: " + previousChallengeOwner);
- if (previousChallengeOwner != null) {
- final ClientMonitorCallbackConverter listener =
- previousChallengeOwner.getListener();
- if (listener == null) {
- Slog.w(TAG, "Listener is null");
- } else {
- try {
- listener.onChallengeInterruptFinished(mSensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify interrupt finished", e);
- }
- }
}
}
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index 3e0064e496c7..f418104834e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -17,16 +17,20 @@
package com.android.server.biometrics.sensors.face.hidl;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.IFaceServiceReceiver;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.internal.util.Preconditions;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Face-specific generateChallenge client supporting the
* {@link android.hardware.biometrics.face.V1_0} HIDL interface.
@@ -34,40 +38,70 @@ import com.android.server.biometrics.sensors.GenerateChallengeClient;
public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> {
private static final String TAG = "FaceGenerateChallengeClient";
- private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
+ static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
+ private static final Callback EMPTY_CALLBACK = new Callback() {
+ };
- // If `this` FaceGenerateChallengeClient was invoked while an existing in-flight challenge
- // was not revoked yet, store a reference to the interrupted client here. Notify the interrupted
- // client when `this` challenge is revoked.
- @Nullable private final FaceGenerateChallengeClient mInterruptedClient;
+ private final long mCreatedAt;
+ private List<IFaceServiceReceiver> mWaiting;
+ private Long mChallengeResult;
FaceGenerateChallengeClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, @Nullable FaceGenerateChallengeClient interruptedClient) {
+ int sensorId, long now) {
super(context, lazyDaemon, token, listener, userId, owner, sensorId);
- mInterruptedClient = interruptedClient;
- }
-
- @Nullable
- public FaceGenerateChallengeClient getInterruptedClient() {
- return mInterruptedClient;
+ mCreatedAt = now;
+ mWaiting = new ArrayList<>();
}
@Override
protected void startHalOperation() {
+ mChallengeResult = null;
try {
- final long challenge = getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
- try {
- getListener().onChallengeGenerated(getSensorId(), challenge);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- mCallback.onClientFinished(this, false /* success */);
+ mChallengeResult = getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
+ // send the result to the original caller via mCallback and any waiting callers
+ // that called reuseResult
+ sendChallengeResult(getListener(), mCallback);
+ for (IFaceServiceReceiver receiver : mWaiting) {
+ sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
}
} catch (RemoteException e) {
Slog.e(TAG, "generateChallenge failed", e);
mCallback.onClientFinished(this, false /* success */);
+ } finally {
+ mWaiting = null;
+ }
+ }
+
+ /** @return An arbitrary time value for caching provided to the constructor. */
+ public long getCreatedAt() {
+ return mCreatedAt;
+ }
+
+ /**
+ * Reuse the result of this operation when it is available. The receiver will be notified
+ * immediately if a challenge has already been generated.
+ *
+ * @param receiver receiver to be notified of challenge result
+ */
+ public void reuseResult(@NonNull IFaceServiceReceiver receiver) {
+ if (mWaiting != null) {
+ mWaiting.add(receiver);
+ } else {
+ sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
+ }
+ }
+
+ private void sendChallengeResult(@NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull Callback ownerCallback) {
+ Preconditions.checkState(mChallengeResult != null, "result not available");
+ try {
+ receiver.onChallengeGenerated(getSensorId(), getTargetUserId(), mChallengeResult);
+ ownerCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ ownerCallback.onClientFinished(this, false /* success */);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index e34afc09eec1..29f2f20b8a75 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -100,7 +100,7 @@ class BiometricTestSessionImpl extends ITestSession.Stub {
}
@Override
- public void onChallengeGenerated(int sensorId, long challenge) {
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 293b57d0e890..6d0148190a60 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -53,7 +53,7 @@ class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISessio
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
- getListener().onChallengeGenerated(sensorId, challenge);
+ getListener().onChallengeGenerated(sensorId, userId, challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send challenge", e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index ad4f679f075f..c00daffb867f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -101,7 +101,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
}
@Override
- public void onChallengeGenerated(int sensorId, long challenge) {
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index 3584397eea81..db2f0455a475 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -48,7 +48,7 @@ public class FingerprintGenerateChallengeClient
try {
final long challenge = getFreshDaemon().preEnroll();
try {
- getListener().onChallengeGenerated(getSensorId(), challenge);
+ getListener().onChallengeGenerated(getSensorId(), getTargetUserId(), challenge);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
index 5abc438579e8..2bdeab4703a8 100644
--- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -98,19 +98,7 @@ public class BiometricDeferredQueue {
}
@Override
- public void onChallengeInterrupted(int sensorId) {
- Slog.w(TAG, "Challenge interrupted, sensor: " + sensorId);
- // Consider re-attempting generateChallenge/resetLockout/revokeChallenge
- // when onChallengeInterruptFinished is invoked
- }
-
- @Override
- public void onChallengeInterruptFinished(int sensorId) {
- Slog.w(TAG, "Challenge interrupt finished, sensor: " + sensorId);
- }
-
- @Override
- public void onGenerateChallengeResult(int sensorId, long challenge) {
+ public void onGenerateChallengeResult(int sensorId, int userId, long challenge) {
if (!sensorIds.contains(sensorId)) {
Slog.e(TAG, "Unknown sensorId received: " + sensorId);
return;
@@ -128,10 +116,6 @@ public class BiometricDeferredQueue {
}
sensorIds.remove(sensorId);
- // Challenge is only required for IBiometricsFace@1.0 (and not IFace AIDL). The
- // IBiometricsFace@1.0 HAL does not require userId to revokeChallenge, so passing
- // in 0 is OK.
- final int userId = 0;
faceManager.revokeChallenge(sensorId, userId, challenge);
if (sensorIds.isEmpty()) {
@@ -234,18 +218,12 @@ public class BiometricDeferredQueue {
}
}
- /**
- * For devices on {@link android.hardware.biometrics.face.V1_0} which only support a single
- * in-flight challenge, we generate a single challenge to reset lockout for all profiles. This
- * hopefully reduces/eliminates issues such as overwritten challenge, incorrectly revoked
- * challenge, or other race conditions.
- */
private void processPendingLockoutsForFace(List<UserAuthInfo> pendingResetLockouts) {
if (mFaceManager != null) {
if (mFaceResetLockoutTask != null) {
// This code will need to be updated if this problem ever occurs.
- Slog.w(TAG, "mFaceGenerateChallengeCallback not null, previous operation may be"
- + " stuck");
+ Slog.w(TAG,
+ "mFaceGenerateChallengeCallback not null, previous operation may be stuck");
}
final List<FaceSensorPropertiesInternal> faceSensorProperties =
mFaceManager.getSensorPropertiesInternal();
@@ -258,12 +236,13 @@ public class BiometricDeferredQueue {
mSpManager, sensorIds, pendingResetLockouts);
for (final FaceSensorPropertiesInternal prop : faceSensorProperties) {
if (prop.resetLockoutRequiresHardwareAuthToken) {
- if (prop.resetLockoutRequiresChallenge) {
- // Generate a challenge for each sensor. The challenge does not need to be
- // per-user, since the HAT returned by gatekeeper contains userId.
- mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask);
- } else {
- for (UserAuthInfo user : pendingResetLockouts) {
+ for (UserAuthInfo user : pendingResetLockouts) {
+ if (prop.resetLockoutRequiresChallenge) {
+ Slog.d(TAG, "Generating challenge for sensor: " + prop.sensorId
+ + ", user: " + user.userId);
+ mFaceManager.generateChallenge(prop.sensorId, user.userId,
+ mFaceResetLockoutTask);
+ } else {
Slog.d(TAG, "Resetting face lockout for sensor: " + prop.sensorId
+ ", user: " + user.userId);
final byte[] hat = requestHatFromGatekeeperPassword(mSpManager, user,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 0b59be65b887..39c51d5f5e5e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -18,6 +18,10 @@ package com.android.server.biometrics.sensors.face.hidl;
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -26,6 +30,7 @@ import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.IBinder;
import android.os.UserManager;
@@ -42,8 +47,12 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.IntStream;
@Presubmit
@SmallTest
@@ -51,6 +60,7 @@ public class Face10Test {
private static final String TAG = "Face10Test";
private static final int SENSOR_ID = 1;
+ private static final int USER_ID = 20;
@Mock
private Context mContext;
@@ -86,10 +96,18 @@ public class Face10Test {
FaceSensorProperties.TYPE_UNKNOWN, supportsFaceDetection, supportsSelfIllumination,
resetLockoutRequiresChallenge);
+ Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler);
mBinder = new Binder();
}
+ private void tick(long seconds) {
+ waitForIdle();
+ Face10.sSystemClock = Clock.fixed(Instant.ofEpochSecond(
+ Face10.sSystemClock.instant().getEpochSecond() + seconds),
+ ZoneId.of("PST"));
+ }
+
@Test
public void getAuthenticatorId_doesNotCrashWhenIdNotFound() {
assertEquals(0, mFace10.getAuthenticatorId(0 /* sensorId */, 111 /* userId */));
@@ -104,6 +122,59 @@ public class Face10Test {
}
@Test
+ public void scheduleGenerateChallenge_cachesResult() {
+ final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
+ .mapToObj(i -> mock(IFaceServiceReceiver.class))
+ .toArray(IFaceServiceReceiver[]::new);
+ for (IFaceServiceReceiver mock : mocks) {
+ mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
+ tick(10);
+ }
+ tick(120);
+ mFace10.scheduleGenerateChallenge(
+ SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
+ waitForIdle();
+
+ verify(mScheduler, times(2))
+ .scheduleClientMonitor(isA(FaceGenerateChallengeClient.class), any());
+ }
+
+ @Test
+ public void scheduleRevokeChallenge_waitsUntilEmpty() {
+ final long challenge = 22;
+ final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
+ .mapToObj(i -> mock(IFaceServiceReceiver.class))
+ .toArray(IFaceServiceReceiver[]::new);
+ for (IFaceServiceReceiver mock : mocks) {
+ mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
+ tick(10);
+ }
+ for (IFaceServiceReceiver mock : mocks) {
+ mFace10.scheduleRevokeChallenge(SENSOR_ID, USER_ID, mBinder, TAG, challenge);
+ tick(10);
+ }
+ waitForIdle();
+
+ verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
+ }
+
+ @Test
+ public void scheduleRevokeChallenge_doesNotWaitForever() {
+ mFace10.scheduleGenerateChallenge(
+ SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
+ mFace10.scheduleGenerateChallenge(
+ SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
+ tick(10000);
+ mFace10.scheduleGenerateChallenge(
+ SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
+ mFace10.scheduleRevokeChallenge(
+ SENSOR_ID, USER_ID, mBinder, TAG, 8 /* challenge */);
+ waitForIdle();
+
+ verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
+ }
+
+ @Test
public void halServiceDied_resetsScheduler() {
// It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
// serviceDied directly.
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
new file mode 100644
index 000000000000..55dc03595b3d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.face.hidl;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class FaceGenerateChallengeClientTest {
+
+ private static final String TAG = "FaceGenerateChallengeClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final long START_TIME = 5000;
+ private static final long CHALLENGE = 200;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ @Mock
+ private IBiometricsFace mIBiometricsFace;
+ @Mock
+ private IFaceServiceReceiver mClientReceiver;
+ @Mock
+ private IFaceServiceReceiver mOtherReceiver;
+ @Mock
+ private BaseClientMonitor.Callback mMonitorCallback;
+
+ private FaceGenerateChallengeClient mClient;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ final OptionalUint64 challenge = new OptionalUint64();
+ challenge.value = CHALLENGE;
+ when(mIBiometricsFace.generateChallenge(anyInt())).thenReturn(challenge);
+
+ mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
+ new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
+ TAG, SENSOR_ID, START_TIME);
+ }
+
+ @Test
+ public void getCreatedAt() {
+ assertEquals(START_TIME, mClient.getCreatedAt());
+ }
+
+ @Test
+ public void reuseResult_whenNotReady() throws Exception {
+ mClient.reuseResult(mOtherReceiver);
+ verify(mOtherReceiver, never()).onChallengeGenerated(anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void reuseResult_whenReady() throws Exception {
+ mClient.start(mMonitorCallback);
+ mClient.reuseResult(mOtherReceiver);
+ verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
+ }
+
+ @Test
+ public void reuseResult_whenReallyReady() throws Exception {
+ mClient.reuseResult(mOtherReceiver);
+ mClient.start(mMonitorCallback);
+ verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
+ }
+}