summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Wenhui Yang <wenhuiy@google.com> 2023-08-02 18:01:27 +0000
committer Wenhui Yang <wenhuiy@google.com> 2023-08-16 06:11:41 +0000
commit0c596f11e227cad08991a327a19bd68968847a19 (patch)
treeeb18ef748ad3826b307cd1ec9c4754dee3f3ea65
parent53bc9fa614f54bf797bb814a6e129469b0453035 (diff)
[3/n] FRR notifications
Add methods in BiometricNotificationUtils for face and fp enrollment. Bug: 258872351 Test: atest AuthenticationStatsCollectorTest Change-Id: Id1100a6cb8113c4c52bfdb5d64b7e8b086d3d559
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java50
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java36
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java38
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java115
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java185
14 files changed, 431 insertions, 37 deletions
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9cc78ad4d852..3babf49da015 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -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" />
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 85125d294015..0380756addea 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -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);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java
new file mode 100644
index 000000000000..90e18604d945
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java
@@ -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);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java
new file mode 100644
index 000000000000..7b420468f628
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java
@@ -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);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index f516a4930a58..230ba63fe5e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -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);
+ }
+
+ /**
+ * 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);
- showNotificationHelper(context, name, title, content, pendingIntent, channelName,
- RE_ENROLL_NOTIFICATION_TAG);
+ 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(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(RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+ notificationManager.cancelAsUser(FINGERPRINT_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
UserHandle.CURRENT);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 722c9afbeaf8..f55cf0549382 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -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
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 a7d160c4fa60..28f0a4dadbd5 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
@@ -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;
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 10991d5f9133..808626120c1e 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
@@ -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,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 16d2f7a03c6d..27b9c79516af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -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) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index f9e08d69ef48..46ff6b4fab1a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -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) {
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 2d062db12cdc..5f4b89439fd0 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
@@ -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);
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 4b07dca75e9e..d0b71fcf2dbb 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
@@ -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);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 6fee84a5e057..382e7e2121f4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -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) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index a578f9a6bf39..285a20c0f9cb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -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);
+ }
}