[3/n] FRR notifications

Add methods in BiometricNotificationUtils for face and fp enrollment.

Bug: 258872351
Test: atest AuthenticationStatsCollectorTest
Change-Id: Id1100a6cb8113c4c52bfdb5d64b7e8b086d3d559
This commit is contained in:
Wenhui Yang
2023-08-02 18:01:27 +00:00
parent 53bc9fa614
commit 0c596f11e2
14 changed files with 431 additions and 37 deletions

View File

@@ -2623,6 +2623,8 @@
<java-symbol type="string" name="fingerprint_recalibrate_notification_name" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_title" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_content" />
<java-symbol type="string" name="fingerprint_setup_notification_title" />
<java-symbol type="string" name="fingerprint_setup_notification_content" />
<java-symbol type="string" name="fingerprint_error_power_pressed" />
<!-- Fingerprint config -->
@@ -2690,6 +2692,7 @@
<java-symbol type="string" name="face_authenticated_no_confirmation_required" />
<java-symbol type="string" name="face_authenticated_confirmation_required" />
<java-symbol type="string" name="face_error_security_update_required" />
<java-symbol type="string" name="face_setup_notification_title" />
<java-symbol type="string" name="config_biometric_prompt_ui_package" />
<java-symbol type="array" name="config_biometric_sensors" />

View File

@@ -19,9 +19,13 @@ package com.android.server.biometrics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.BiometricNotification;
import java.util.HashMap;
import java.util.Map;
@@ -50,13 +54,16 @@ public class AuthenticationStatsCollector {
@NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
@NonNull private AuthenticationStatsPersister mAuthenticationStatsPersister;
@NonNull private BiometricNotification mBiometricNotification;
public AuthenticationStatsCollector(@NonNull Context context, int modality) {
public AuthenticationStatsCollector(@NonNull Context context, int modality,
@NonNull BiometricNotification biometricNotification) {
mContext = context;
mThreshold = context.getResources()
.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
mUserAuthenticationStatsMap = new HashMap<>();
mModality = modality;
mBiometricNotification = biometricNotification;
}
private void initializeUserAuthenticationStatsMap() {
@@ -86,16 +93,45 @@ public class AuthenticationStatsCollector {
sendNotificationIfNeeded(userId);
}
/** Check if a notification should be sent after a calculation cycle. */
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.
}
if (authenticationStats.getTotalAttempts() < MINIMUM_ATTEMPTS) {
return;
}
// Don't send notification if FRR below the threshold.
if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS
|| authenticationStats.getFrr() < mThreshold) {
authenticationStats.resetData();
return;
}
authenticationStats.resetData();
final PackageManager packageManager = mContext.getPackageManager();
// Don't send notification to single-modality devices.
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
|| !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
return;
}
final FaceManager faceManager = mContext.getSystemService(FaceManager.class);
final boolean hasEnrolledFace = faceManager.hasEnrolledTemplates(userId);
final FingerprintManager fingerprintManager = mContext
.getSystemService(FingerprintManager.class);
final boolean hasEnrolledFingerprint = fingerprintManager.hasEnrolledTemplates(userId);
// Don't send notification when both face and fingerprint are enrolled.
if (hasEnrolledFace && hasEnrolledFingerprint) {
return;
}
if (hasEnrolledFace && !hasEnrolledFingerprint) {
mBiometricNotification.sendFpEnrollNotification(mContext);
} else if (!hasEnrolledFace && hasEnrolledFingerprint) {
mBiometricNotification.sendFaceEnrollNotification(mContext);
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.sensors;
import android.annotation.NonNull;
import android.content.Context;
/**
* Interface for biometrics to send notifications.
*/
public interface BiometricNotification {
/**
* Sends a face enrollment notification.
*/
void sendFaceEnrollNotification(@NonNull Context context);
/**
* Sends a fingerprint enrollment notification.
*/
void sendFpEnrollNotification(@NonNull Context context);
}

View File

@@ -0,0 +1,38 @@
/*
* 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.sensors;
import android.annotation.NonNull;
import android.content.Context;
import com.android.server.biometrics.AuthenticationStatsCollector;
/**
* Implementation to send biometric notifications for {@link AuthenticationStatsCollector}.
*/
public class BiometricNotificationImpl implements BiometricNotification {
@Override
public void sendFaceEnrollNotification(@NonNull Context context) {
BiometricNotificationUtils.showFaceEnrollNotification(context);
}
@Override
public void sendFpEnrollNotification(@NonNull Context context) {
BiometricNotificationUtils.showFingerprintEnrollNotification(context);
}
}

View File

@@ -35,9 +35,22 @@ import com.android.internal.R;
public class BiometricNotificationUtils {
private static final String TAG = "BiometricNotificationUtils";
private static final String RE_ENROLL_NOTIFICATION_TAG = "FaceService";
private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintService";
private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll";
private static final String FACE_ENROLL_NOTIFICATION_TAG = "FaceEnroll";
private static final String FINGERPRINT_ENROLL_NOTIFICATION_TAG = "FingerprintEnroll";
private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration";
private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
private static final String FINGERPRINT_SETTINGS_ACTION =
"android.settings.FINGERPRINT_SETTINGS";
private static final String FACE_ENROLL_ACTION = "android.settings.FACE_ENROLL";
private static final String FINGERPRINT_ENROLL_ACTION = "android.settings.FINGERPRINT_ENROLL";
private static final String SETTINGS_PACKAGE = "com.android.settings";
private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel";
private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel";
private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL =
"FingerprintBadCalibrationNotificationChannel";
private static final int NOTIFICATION_ID = 1;
private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
private static long sLastAlertTime = 0;
@@ -56,18 +69,67 @@ public class BiometricNotificationUtils {
final String content =
context.getString(R.string.face_recalibrate_notification_content);
final Intent intent = new Intent("android.settings.FACE_SETTINGS");
intent.setPackage("com.android.settings");
final Intent intent = new Intent(FACE_SETTINGS_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
intent.putExtra(KEY_RE_ENROLL_FACE, true);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
final String channelName = "FaceEnrollNotificationChannel";
showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
FACE_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
}
showNotificationHelper(context, name, title, content, pendingIntent, channelName,
RE_ENROLL_NOTIFICATION_TAG);
/**
* Shows a face enrollment notification.
*/
public static void showFaceEnrollNotification(@NonNull Context context) {
final String name =
context.getString(R.string.face_recalibrate_notification_name);
final String title =
context.getString(R.string.fingerprint_setup_notification_title);
final String content =
context.getString(R.string.face_setup_notification_title);
final Intent intent = new Intent(FACE_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
FACE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
}
/**
* Shows a fingerprint enrollment notification.
*/
public static void showFingerprintEnrollNotification(@NonNull Context context) {
final String name =
context.getString(R.string.fingerprint_recalibrate_notification_name);
final String title =
context.getString(R.string.fingerprint_setup_notification_title);
final String content =
context.getString(R.string.fingerprint_setup_notification_content);
final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent,
FINGERPRINT_ENROLL_CHANNEL, FINGERPRINT_ENROLL_NOTIFICATION_TAG,
Notification.VISIBILITY_PUBLIC);
}
/**
@@ -93,22 +155,21 @@ public class BiometricNotificationUtils {
final String content =
context.getString(R.string.fingerprint_recalibrate_notification_content);
final Intent intent = new Intent("android.settings.FINGERPRINT_SETTINGS");
intent.setPackage("com.android.settings");
final Intent intent = new Intent(FINGERPRINT_SETTINGS_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
final String channelName = "FingerprintBadCalibrationNotificationChannel";
showNotificationHelper(context, name, title, content, pendingIntent, channelName,
BAD_CALIBRATION_NOTIFICATION_TAG);
showNotificationHelper(context, name, title, content, pendingIntent,
FINGERPRINT_BAD_CALIBRATION_CHANNEL, BAD_CALIBRATION_NOTIFICATION_TAG,
Notification.VISIBILITY_SECRET);
}
private static void showNotificationHelper(Context context, String name, String title,
String content, PendingIntent pendingIntent, String channelName,
String notificationTag) {
String notificationTag, int visibility) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
final NotificationChannel channel = new NotificationChannel(channelName, name,
@@ -123,7 +184,7 @@ public class BiometricNotificationUtils {
.setAutoCancel(true)
.setCategory(Notification.CATEGORY_SYSTEM)
.setContentIntent(pendingIntent)
.setVisibility(Notification.VISIBILITY_SECRET)
.setVisibility(visibility)
.build();
notificationManager.createNotificationChannel(channel);
@@ -134,10 +195,30 @@ public class BiometricNotificationUtils {
/**
* Cancels a face re-enrollment notification
*/
public static void cancelReEnrollNotification(@NonNull Context context) {
public static void cancelFaceReEnrollNotification(@NonNull Context context) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
notificationManager.cancelAsUser(RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
notificationManager.cancelAsUser(FACE_RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
UserHandle.CURRENT);
}
/**
* Cancels a face enrollment notification
*/
public static void cancelFaceEnrollNotification(@NonNull Context context) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
notificationManager.cancelAsUser(FACE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
UserHandle.CURRENT);
}
/**
* Cancels a fingerprint enrollment notification
*/
public static void cancelFingerprintEnrollNotification(@NonNull Context context) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
notificationManager.cancelAsUser(FINGERPRINT_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
UserHandle.CURRENT);
}

View File

@@ -109,7 +109,8 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
public void start(@NonNull ClientMonitorCallback callback) {
super.start(callback);
BiometricNotificationUtils.cancelReEnrollNotification(getContext());
BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
}
@NonNull

View File

@@ -56,6 +56,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -177,7 +178,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mDaemon = daemon;
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
BiometricsProtoEnums.MODALITY_FACE);
BiometricsProtoEnums.MODALITY_FACE, new BiometricNotificationImpl());
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;

View File

@@ -62,7 +62,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -367,7 +367,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
});
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
BiometricsProtoEnums.MODALITY_FACE);
BiometricsProtoEnums.MODALITY_FACE, new BiometricNotificationImpl());
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
@@ -615,8 +615,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
BiometricNotificationUtils.cancelReEnrollNotification(mContext);
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,

View File

@@ -33,6 +33,7 @@ import com.android.internal.R;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -71,6 +72,14 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
.getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
}
@Override
public void start(@NonNull ClientMonitorCallback callback) {
super.start(callback);
BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
}
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {

View File

@@ -104,6 +104,13 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
}
}
@Override
public void start(@NonNull ClientMonitorCallback callback) {
super.start(callback);
BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
}
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {

View File

@@ -64,6 +64,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -184,7 +185,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mDaemon = daemon;
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
BiometricsProtoEnums.MODALITY_FINGERPRINT);
BiometricsProtoEnums.MODALITY_FINGERPRINT, new BiometricNotificationImpl());
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);

View File

@@ -66,6 +66,7 @@ import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -354,7 +355,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
});
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
BiometricsProtoEnums.MODALITY_FINGERPRINT);
BiometricsProtoEnums.MODALITY_FINGERPRINT, new BiometricNotificationImpl());
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);

View File

@@ -81,6 +81,13 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
}
}
@Override
public void start(@NonNull ClientMonitorCallback callback) {
super.start(callback);
BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
}
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {

View File

@@ -22,15 +22,23 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptySet;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import com.android.internal.R;
import com.android.server.biometrics.sensors.BiometricNotification;
import org.junit.Before;
import org.junit.Test;
@@ -50,21 +58,39 @@ public class AuthenticationStatsCollectorTest {
@Mock
private Resources mResources;
@Mock
private PackageManager mPackageManager;
@Mock
private FingerprintManager mFingerprintManager;
@Mock
private FaceManager mFaceManager;
@Mock
private SharedPreferences mSharedPreferences;
@Mock
private BiometricNotification mBiometricNotification;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
.thenReturn(FRR_THRESHOLD);
when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold),
anyInt(), anyInt())).thenReturn(FRR_THRESHOLD);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mContext.getSystemServiceName(FingerprintManager.class))
.thenReturn(Context.FINGERPRINT_SERVICE);
when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
.thenReturn(mFingerprintManager);
when(mContext.getSystemServiceName(FaceManager.class)).thenReturn(Context.FACE_SERVICE);
when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
when(mContext.getSharedPreferences(any(File.class), anyInt()))
.thenReturn(mSharedPreferences);
when(mSharedPreferences.getStringSet(anyString(), anySet())).thenReturn(emptySet());
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
0 /* modality */);
0 /* modality */, mBiometricNotification);
}
@@ -73,7 +99,7 @@ public class AuthenticationStatsCollectorTest {
// Assert that the user doesn't exist in the map initially.
assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated */);
AuthenticationStats authenticationStats =
mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
@@ -88,7 +114,7 @@ public class AuthenticationStatsCollectorTest {
// Assert that the user doesn't exist in the map initially.
assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
AuthenticationStats authenticationStats =
mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
@@ -98,4 +124,153 @@ public class AuthenticationStatsCollectorTest {
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(1);
assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
}
@Test
public void authenticate_frrNotExceeded_notificationNotExceeded_shouldNotSendNotification() {
mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
40 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
0 /* modality */));
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
// Assert that no notification should be sent.
verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
// Assert that data has been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@Test
public void authenticate_frrExceeded_notificationExceeded_shouldNotSendNotification() {
mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
400 /* rejectedAttempts */, 2 /* enrollmentNotifications */,
0 /* modality */));
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
// Assert that no notification should be sent.
verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
// Assert that data has been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@Test
public void authenticate_frrExceeded_bothBiometricsEnrolled_shouldNotSendNotification() {
mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
0 /* modality */));
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
// Assert that no notification should be sent.
verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
// Assert that data has been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@Test
public void authenticate_frrExceeded_singleModality_shouldNotSendNotification() {
mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
0 /* modality */));
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
// Assert that no notification should be sent.
verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
// Assert that data has been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@Test
public void authenticate_frrExceeded_faceEnrolled_shouldSendFpNotification() {
mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
0 /* modality */));
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
// Assert that fingerprint enrollment notification should be sent.
verify(mBiometricNotification, times(1))
.sendFpEnrollNotification(mContext);
verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
// Assert that data has been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@Test
public void authenticate_frrExceeded_fpEnrolled_shouldSendFaceNotification() {
mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
0 /* modality */));
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
// Assert that fingerprint enrollment notification should be sent.
verify(mBiometricNotification, times(1))
.sendFaceEnrollNotification(mContext);
verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
// Assert that data has been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
}