Merge "[1/n] FRR data capture" into udc-qpr-dev am: 811b771207 am: c7b7bb2796
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23841345
Change-Id: I72c3bdfb9a867b966d74c3fd08b9af1331dda921
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7ea8f8d..72333fb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5372,6 +5372,12 @@
<!-- Default value for performant auth feature. -->
<bool name="config_performantAuthDefault">false</bool>
+ <!-- Threshold for false rejection rate (FRR) of biometric authentication. Applies for both
+ fingerprint and face. If a dual-modality device only enrolled a single biometric and
+ experiences high FRR (above threshold), system notification will be sent to encourage user
+ to enroll the other eligible biometric. -->
+ <fraction name="config_biometricNotificationFrrThreshold">30%</fraction>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 52f4db5..28a9abb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2594,6 +2594,9 @@
<java-symbol type="string" name="biometric_error_device_not_secured" />
<java-symbol type="string" name="biometric_error_generic" />
+ <!-- Biometric FRR config -->
+ <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+
<!-- Device credential strings for BiometricManager -->
<java-symbol type="string" name="screen_lock_app_setting_name" />
<java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
new file mode 100644
index 0000000..137a418
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 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;
+
+/**
+ * Utility class for on-device biometric authentication data, including total authentication,
+ * rejections, and the number of sent enrollment notifications.
+ */
+public class AuthenticationStats {
+
+ private final int mUserId;
+ private int mTotalAttempts;
+ private int mRejectedAttempts;
+ private int mEnrollmentNotifications;
+ private final int mModality;
+
+ public AuthenticationStats(final int userId, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, final int modality) {
+ mUserId = userId;
+ mTotalAttempts = totalAttempts;
+ mRejectedAttempts = rejectedAttempts;
+ mEnrollmentNotifications = enrollmentNotifications;
+ mModality = modality;
+ }
+
+ public AuthenticationStats(final int userId, final int modality) {
+ mUserId = userId;
+ mTotalAttempts = 0;
+ mRejectedAttempts = 0;
+ mEnrollmentNotifications = 0;
+ mModality = modality;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public int getTotalAttempts() {
+ return mTotalAttempts;
+ }
+
+ public int getRejectedAttempts() {
+ return mRejectedAttempts;
+ }
+
+ public int getEnrollmentNotifications() {
+ return mEnrollmentNotifications;
+ }
+
+ public int getModality() {
+ return mModality;
+ }
+
+ /** Calculate FRR. */
+ public float getFrr() {
+ if (mTotalAttempts > 0) {
+ return mRejectedAttempts / (float) mTotalAttempts;
+ } else {
+ return -1.0f;
+ }
+ }
+
+ /** Update total authentication attempts and rejections. */
+ public void authenticate(boolean authenticated) {
+ if (!authenticated) {
+ mRejectedAttempts++;
+ }
+ mTotalAttempts++;
+ }
+
+ /** Reset total authentication attempts and rejections. */
+ public void resetData() {
+ mTotalAttempts = 0;
+ mRejectedAttempts = 0;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
new file mode 100644
index 0000000..c9cd814
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculate and collect on-device False Rejection Rates (FRR).
+ * FRR = All [given biometric modality] unlock failures / all [given biometric modality] unlock
+ * attempts.
+ */
+public class AuthenticationStatsCollector {
+
+ private static final String TAG = "AuthenticationStatsCollector";
+
+ // The minimum number of attempts that will calculate the FRR and trigger the notification.
+ private static final int MINIMUM_ATTEMPTS = 500;
+ // The maximum number of eligible biometric enrollment notification can be sent.
+ private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+
+ private final float mThreshold;
+ private final int mModality;
+
+ @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
+
+ public AuthenticationStatsCollector(@NonNull Context context, int modality) {
+ mThreshold = context.getResources()
+ .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
+ mUserAuthenticationStatsMap = new HashMap<>();
+ mModality = modality;
+ }
+
+ /** Update total authentication and rejected attempts. */
+ public void authenticate(int userId, boolean authenticated) {
+ // Check if this is a new user.
+ if (!mUserAuthenticationStatsMap.containsKey(userId)) {
+ mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
+ }
+
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+
+ authenticationStats.authenticate(authenticated);
+
+ persistDataIfNeeded(userId);
+ sendNotificationIfNeeded(userId);
+ }
+
+ private void sendNotificationIfNeeded(int userId) {
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+ if (authenticationStats.getTotalAttempts() >= MINIMUM_ATTEMPTS) {
+ // Send notification if FRR exceeds the threshold
+ if (authenticationStats.getEnrollmentNotifications() < MAXIMUM_ENROLLMENT_NOTIFICATIONS
+ && authenticationStats.getFrr() >= mThreshold) {
+ // TODO(wenhuiy): Send notifications.
+ }
+
+ authenticationStats.resetData();
+ }
+ }
+
+ private void persistDataIfNeeded(int userId) {
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+ if (authenticationStats.getTotalAttempts() % 50 == 0) {
+ // TODO(wenhuiy): Persist data.
+ }
+ }
+
+ /**
+ * Only being used in tests. Callers should not make any changes to the returned
+ * authentication stats.
+ *
+ * @return AuthenticationStats of the user, or null if the stats doesn't exist.
+ */
+ @Nullable
+ @VisibleForTesting
+ AuthenticationStats getAuthenticationStatsForUser(int userId) {
+ return mUserAuthenticationStatsMap.getOrDefault(userId, null);
+ }
+
+ @VisibleForTesting
+ void setAuthenticationStatsForUser(int userId, AuthenticationStats authenticationStats) {
+ mUserAuthenticationStatsMap.put(userId, authenticationStats);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index c76a2e3..87037af 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -27,6 +27,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
/**
@@ -41,6 +42,7 @@
private final int mStatsAction;
private final int mStatsClient;
private final BiometricFrameworkStatsLogger mSink;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
@NonNull private final ALSProbe mALSProbe;
private long mFirstAcquireTimeMs;
@@ -49,7 +51,8 @@
/** Get a new logger with all unknown fields (for operations that do not require logs). */
public static BiometricLogger ofUnknown(@NonNull Context context) {
return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
+ null /* AuthenticationStatsCollector */);
}
/**
@@ -64,26 +67,32 @@
* @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
*/
public BiometricLogger(
- @NonNull Context context, int statsModality, int statsAction, int statsClient) {
+ @NonNull Context context, int statsModality, int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
this(statsModality, statsAction, statsClient,
BiometricFrameworkStatsLogger.getInstance(),
+ authenticationStatsCollector,
context.getSystemService(SensorManager.class));
}
@VisibleForTesting
BiometricLogger(
int statsModality, int statsAction, int statsClient,
- BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) {
+ BiometricFrameworkStatsLogger logSink,
+ @NonNull AuthenticationStatsCollector statsCollector,
+ SensorManager sensorManager) {
mStatsModality = statsModality;
mStatsAction = statsAction;
mStatsClient = statsClient;
mSink = logSink;
+ mAuthenticationStatsCollector = statsCollector;
mALSProbe = new ALSProbe(sensorManager);
}
/** Creates a new logger with the action replaced with the new action. */
public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
- return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+ return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient,
+ null /* AuthenticationStatsCollector */);
}
/** Disable logging metrics and only log critical events, such as system health issues. */
@@ -192,10 +201,13 @@
public void logOnAuthenticated(Context context, OperationContextExt operationContext,
boolean authenticated, boolean requireConfirmation, int targetUserId,
boolean isBiometricPrompt) {
+ // Do not log metrics when fingerprint enrollment reason is ENROLL_FIND_SENSOR
if (!mShouldLogMetrics) {
return;
}
+ mAuthenticationStatsCollector.authenticate(targetUserId, authenticated);
+
int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
if (!authenticated) {
authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 33ed63c..a7d160c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -49,6 +49,7 @@
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -112,6 +113,8 @@
private final BiometricContext mBiometricContext;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final AuthenticationStatsCollector mAuthenticationStatsCollector;
@Nullable
private IFace mDaemon;
@@ -173,6 +176,9 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FACE);
+
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
@@ -283,7 +289,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -341,7 +348,8 @@
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
@@ -372,7 +380,8 @@
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
scheduleForSensor(sensorId, client);
});
@@ -386,7 +395,8 @@
mFaceSensors.get(sensorId).getLazySession(), token, userId,
opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
@@ -407,7 +417,8 @@
opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent);
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
mBiometricStateCallback, new ClientMonitorCallback() {
@@ -443,7 +454,8 @@
final FaceDetectClient client = new FaceDetectClient(mContext,
mFaceSensors.get(sensorId).getLazySession(),
token, id, callback, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -471,7 +483,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
@@ -540,7 +553,8 @@
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -554,7 +568,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
@@ -624,7 +639,8 @@
mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
FaceUtils.getInstance(sensorId),
mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -636,9 +652,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
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 1e33c96..10991d5 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
@@ -52,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -124,6 +125,7 @@
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -364,6 +366,9 @@
mCurrentUserId = UserHandle.USER_NULL;
});
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FACE);
+
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
@@ -554,7 +559,7 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
opPackageName, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, sSystemClock.millis());
mGeneratedChallengeCache = client;
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -586,7 +591,7 @@
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
mLazyDaemon, token, userId, opPackageName, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -617,7 +622,7 @@
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -677,7 +682,8 @@
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
mLazyDaemon, token, requestId, receiver, operationId, restricted,
options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric, mLockoutTracker,
mUsageStats, allowBackgroundAuthentication,
Utils.getCurrentStrength(mSensorId));
@@ -713,7 +719,7 @@
new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
FaceUtils.getLegacyInstance(mSensorId), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -731,7 +737,7 @@
opPackageName,
FaceUtils.getLegacyInstance(mSensorId), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -750,7 +756,7 @@
final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken);
mScheduler.scheduleClientMonitor(client);
});
@@ -821,7 +827,7 @@
final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
@@ -953,7 +959,7 @@
final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, hasEnrolled, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -968,9 +974,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 0421d78..2d062db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -57,6 +57,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -122,6 +123,7 @@
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
private AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -181,6 +183,9 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
for (SensorProps prop : props) {
@@ -338,7 +343,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
@@ -363,7 +369,8 @@
mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
@@ -380,7 +387,7 @@
mFingerprintSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
scheduleForSensor(sensorId, client);
});
@@ -395,7 +402,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), token,
userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
@@ -415,7 +423,7 @@
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
@@ -455,7 +463,8 @@
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -477,7 +486,8 @@
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController,
@@ -566,7 +576,8 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -588,7 +599,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
FingerprintUtils.getInstance(sensorId),
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
@@ -600,9 +612,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
@@ -635,7 +648,8 @@
new FingerprintInvalidationClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 92b216d..4b07dca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -123,6 +124,7 @@
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -351,6 +353,9 @@
mCurrentUserId = UserHandle.USER_NULL;
});
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
@@ -497,7 +502,8 @@
new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
mContext.getOpPackageName(), mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -544,7 +550,8 @@
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
userId, mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, mLockoutTracker);
mScheduler.scheduleClientMonitor(client);
});
@@ -559,7 +566,8 @@
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
@@ -573,7 +581,8 @@
mContext, mLazyDaemon, token, userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
@@ -594,7 +603,7 @@
FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -639,7 +648,8 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mLazyDaemon, token, id, listener, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -660,7 +670,8 @@
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mLazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
mUdfpsOverlayController, mSidefpsController,
@@ -706,7 +717,7 @@
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -726,7 +737,7 @@
FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -741,7 +752,7 @@
mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, callback);
@@ -762,9 +773,10 @@
mBiometricStateCallback));
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
new file mode 100644
index 0000000..99d66c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AuthenticationStatsCollectorTest {
+
+ private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ private static final float FRR_THRESHOLD = 0.2f;
+ private static final int USER_ID_1 = 1;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ 0 /* modality */);
+ }
+
+
+ @Test
+ public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
+ // Assert that the user doesn't exist in the map initially.
+ assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
+
+ AuthenticationStats authenticationStats =
+ mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+ assertEquals(USER_ID_1, authenticationStats.getUserId());
+ assertEquals(1, authenticationStats.getTotalAttempts());
+ assertEquals(0, authenticationStats.getRejectedAttempts());
+ assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ }
+
+ @Test
+ public void authenticate_authenticationFailed_mapShouldBeUpdated() {
+ // Assert that the user doesn't exist in the map initially.
+ assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
+
+ AuthenticationStats authenticationStats =
+ mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+ assertEquals(USER_ID_1, authenticationStats.getUserId());
+ assertEquals(1, authenticationStats.getTotalAttempts());
+ assertEquals(1, authenticationStats.getRejectedAttempts());
+ assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
new file mode 100644
index 0000000..e8e72cb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class AuthenticationStatsTest {
+
+ @Test
+ public void authenticate_statsShouldBeUpdated() {
+ AuthenticationStats authenticationStats =
+ new AuthenticationStats(1 /* userId */ , 0 /* totalAttempts */,
+ 0 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */);
+
+ authenticationStats.authenticate(true /* authenticated */);
+
+ assertEquals(authenticationStats.getTotalAttempts(), 1);
+ assertEquals(authenticationStats.getRejectedAttempts(), 0);
+
+ authenticationStats.authenticate(false /* authenticated */);
+
+ assertEquals(authenticationStats.getTotalAttempts(), 2);
+ assertEquals(authenticationStats.getRejectedAttempts(), 1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 612f717..a508718 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import org.junit.Before;
@@ -65,6 +66,8 @@
@Mock
private BiometricFrameworkStatsLogger mSink;
@Mock
+ private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ @Mock
private SensorManager mSensorManager;
@Mock
private BaseClientMonitor mClient;
@@ -87,7 +90,8 @@
}
private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) {
- return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager);
+ return new BiometricLogger(statsModality, statsAction, statsClient, mSink,
+ mAuthenticationStatsCollector, mSensorManager);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index d1d6e9d..f43120d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
@@ -39,6 +40,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -59,11 +61,15 @@
private static final String TAG = "FaceProviderTest";
+ private static final float FRR_THRESHOLD = 0.2f;
+
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
+ private Resources mResources;
+ @Mock
private IFace mDaemon;
@Mock
private BiometricContext mBiometricContext;
@@ -86,6 +92,10 @@
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
sensor1.commonProps.sensorId = 0;
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 d174533..e558c4d 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
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceSensorProperties;
@@ -41,6 +42,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -65,12 +67,15 @@
private static final String TAG = "Face10Test";
private static final int SENSOR_ID = 1;
private static final int USER_ID = 20;
+ private static final float FRR_THRESHOLD = 0.2f;
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
+ private Resources mResources;
+ @Mock
private BiometricScheduler mScheduler;
@Mock
private BiometricContext mBiometricContext;
@@ -93,6 +98,10 @@
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
final int maxEnrollmentsPerUser = 1;