Loading core/res/res/values/symbols.xml +3 −0 Original line number Diff line number Diff line Loading @@ -2624,6 +2624,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 --> Loading Loading @@ -2691,6 +2693,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" /> Loading services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +65 −7 Original line number Diff line number Diff line Loading @@ -18,10 +18,18 @@ package com.android.server.biometrics; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; 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; Loading Loading @@ -50,13 +58,29 @@ public class AuthenticationStatsCollector { @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap; @NonNull private AuthenticationStatsPersister mAuthenticationStatsPersister; @NonNull private BiometricNotification mBiometricNotification; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (userId != UserHandle.USER_NULL && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) { onUserRemoved(userId); } } }; 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; context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED)); } private void initializeUserAuthenticationStatsMap() { Loading Loading @@ -86,16 +110,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); } } Loading @@ -110,6 +163,11 @@ public class AuthenticationStatsCollector { } } private void onUserRemoved(final int userId) { mUserAuthenticationStatsMap.remove(userId); mAuthenticationStatsPersister.removeFrrStats(userId); } /** * Only being used in tests. Callers should not make any changes to the returned * authentication stats. Loading services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java +23 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,29 @@ public class AuthenticationStatsPersister { return authenticationStatsList; } /** * Remove frr data for a specific user. */ public void removeFrrStats(int userId) { try { // Copy into a new HashSet to avoid iterator exception. Set<String> frrStatsSet = new HashSet<>(readFrrStats()); // Remove the old authentication stat for the user if it exists. for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) { String frrStats = iterator.next(); JSONObject frrStatJson = new JSONObject(frrStats); if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) { iterator.remove(); break; } } mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply(); } catch (JSONException ignored) { } } /** * Persist frr data for a specific user. */ Loading services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * 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); } services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * 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); } } Loading
core/res/res/values/symbols.xml +3 −0 Original line number Diff line number Diff line Loading @@ -2624,6 +2624,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 --> Loading Loading @@ -2691,6 +2693,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" /> Loading
services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +65 −7 Original line number Diff line number Diff line Loading @@ -18,10 +18,18 @@ package com.android.server.biometrics; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; 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; Loading Loading @@ -50,13 +58,29 @@ public class AuthenticationStatsCollector { @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap; @NonNull private AuthenticationStatsPersister mAuthenticationStatsPersister; @NonNull private BiometricNotification mBiometricNotification; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (userId != UserHandle.USER_NULL && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) { onUserRemoved(userId); } } }; 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; context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED)); } private void initializeUserAuthenticationStatsMap() { Loading Loading @@ -86,16 +110,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); } } Loading @@ -110,6 +163,11 @@ public class AuthenticationStatsCollector { } } private void onUserRemoved(final int userId) { mUserAuthenticationStatsMap.remove(userId); mAuthenticationStatsPersister.removeFrrStats(userId); } /** * Only being used in tests. Callers should not make any changes to the returned * authentication stats. Loading
services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java +23 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,29 @@ public class AuthenticationStatsPersister { return authenticationStatsList; } /** * Remove frr data for a specific user. */ public void removeFrrStats(int userId) { try { // Copy into a new HashSet to avoid iterator exception. Set<String> frrStatsSet = new HashSet<>(readFrrStats()); // Remove the old authentication stat for the user if it exists. for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) { String frrStats = iterator.next(); JSONObject frrStatJson = new JSONObject(frrStats); if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) { iterator.remove(); break; } } mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply(); } catch (JSONException ignored) { } } /** * Persist frr data for a specific user. */ Loading
services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * 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); }
services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * 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); } }