Merge changes from topic "user-id-again" into sc-dev
* changes: Add userId parameter to all uses of challenge client. Cache challenge response in Face10 implementation.
This commit is contained in:
@@ -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,21 +140,11 @@ 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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */));
|
||||
@@ -103,6 +121,59 @@ public class Face10Test {
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user