From a0224693b0057e3d863627e6097f05298581a86e Mon Sep 17 00:00:00 2001 From: Wenhui Yang Date: Wed, 28 Jun 2023 05:38:13 +0000 Subject: [PATCH] [1/n] FRR data capture Add AuthenticationStatsCollector and AuthenticationStats to capture and calculate on-device FRR data. Bug: 258872351 Test: atest BiometricLoggerTest Test: atest AuthenticationStatsTest Test: atest AuthenticationStatsCollectorTest Change-Id: Ib1d70fa953c6d350587d5db5ae4450827fd1830d --- core/res/res/values/config.xml | 6 + core/res/res/values/symbols.xml | 3 + .../biometrics/AuthenticationStats.java | 90 +++++++++++++++ .../AuthenticationStatsCollector.java | 106 ++++++++++++++++++ .../biometrics/log/BiometricLogger.java | 20 +++- .../sensors/face/aidl/FaceProvider.java | 41 +++++-- .../biometrics/sensors/face/hidl/Face10.java | 29 +++-- .../fingerprint/aidl/FingerprintProvider.java | 38 +++++-- .../fingerprint/hidl/Fingerprint21.java | 36 ++++-- .../AuthenticationStatsCollectorTest.java | 86 ++++++++++++++ .../biometrics/AuthenticationStatsTest.java | 42 +++++++ .../biometrics/log/BiometricLoggerTest.java | 6 +- .../sensors/face/aidl/FaceProviderTest.java | 10 ++ .../sensors/face/hidl/Face10Test.java | 9 ++ 14 files changed, 470 insertions(+), 52 deletions(-) create mode 100644 services/core/java/com/android/server/biometrics/AuthenticationStats.java create mode 100644 services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java create mode 100644 services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java create mode 100644 services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 130648c74bb1c..cbc6c2a176cc5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5336,6 +5336,12 @@ false + + 30% + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3610eaddff421..9cc78ad4d852c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2581,6 +2581,9 @@ + + + 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 0000000000000..137a418e31aba --- /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 0000000000000..c9cd8148c0f1f --- /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 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 c76a2e38aabca..87037af11a848 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 android.util.Slog; 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 @@ public class BiometricLogger { 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 @@ public class BiometricLogger { /** 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 @@ public class BiometricLogger { * @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 class BiometricLogger { 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 33ed63ce07e01..a7d160c4fa604 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.util.proto.ProtoOutputStream; 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { private final BiometricContext mBiometricContext; @NonNull private final AuthSessionCoordinator mAuthSessionCoordinator; + @NonNull + private final AuthenticationStatsCollector mAuthenticationStatsCollector; @Nullable private IFace mDaemon; @@ -173,6 +176,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { }); } - 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 1e33c96d50adf..10991d5f91337 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 android.view.Surface; 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mCurrentUserId = UserHandle.USER_NULL; }); + mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext, + BiometricsProtoEnums.MODALITY_FACE); + try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); } catch (RemoteException e) { @@ -554,7 +559,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { 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 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { }); } - 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 0421d78b73b9b..2d062db12cdc9 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.Slog; 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator(); mDaemon = daemon; + mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext, + BiometricsProtoEnums.MODALITY_FINGERPRINT); + final List workaroundLocations = getWorkaroundSensorProps(context); for (SensorProps prop : props) { @@ -338,7 +343,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi }); } - 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 92b216df2fdd7..4b07dca75e9e8 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 android.util.proto.ProtoOutputStream; 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mCurrentUserId = UserHandle.USER_NULL; }); + mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext, + BiometricsProtoEnums.MODALITY_FINGERPRINT); + try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); } catch (RemoteException e) { @@ -497,7 +502,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider 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 0000000000000..99d66c5bda19c --- /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 0000000000000..e8e72cb81838a --- /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 612f717226a21..a508718995754 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 android.testing.TestableContext; 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 @@ public class BiometricLoggerTest { @Mock private BiometricFrameworkStatsLogger mSink; @Mock + private AuthenticationStatsCollector mAuthenticationStatsCollector; + @Mock private SensorManager mSensorManager; @Mock private BaseClientMonitor mClient; @@ -87,7 +90,8 @@ public class BiometricLoggerTest { } 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 d1d6e9d41b1f8..f43120d1a7555 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.mock; 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 android.platform.test.annotations.Presubmit; 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 @@ public class FaceProviderTest { 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 @@ public class FaceProviderTest { 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 d174533d909a5..e558c4d64180a 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.verify; 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 android.platform.test.annotations.Presubmit; 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 @@ public class Face10Test { 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 @@ public class Face10Test { 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;