diff options
| author | 2023-08-02 18:01:27 +0000 | |
|---|---|---|
| committer | 2023-08-16 06:11:41 +0000 | |
| commit | 0c596f11e227cad08991a327a19bd68968847a19 (patch) | |
| tree | eb18ef748ad3826b307cd1ec9c4754dee3f3ea65 | |
| parent | 53bc9fa614f54bf797bb814a6e129469b0453035 (diff) | |
[3/n] FRR notifications
Add methods in BiometricNotificationUtils for face and fp enrollment.
Bug: 258872351
Test: atest AuthenticationStatsCollectorTest
Change-Id: Id1100a6cb8113c4c52bfdb5d64b7e8b086d3d559
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); + } } |