[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:
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user