Merge "FRR - Add a device overlay to control QPR1 release targets" into udc-qpr-dev
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f5b0711..8cec8f8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5348,6 +5348,10 @@
          to enroll the other eligible biometric. -->
     <fraction name="config_biometricNotificationFrrThreshold">25%</fraction>
 
+    <!-- Whether to enable the biometric notification for dual-modality device that enrolled a
+         single biometric and experiences high FRR. -->
+    <bool name="config_biometricFrrNotificationEnabled">false</bool>
+
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
     only. The component must be part of a system app. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c78af86..be43a4f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2584,6 +2584,7 @@
 
   <!-- Biometric FRR config -->
   <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+  <java-symbol type="bool" name="config_biometricFrrNotificationEnabled" />
 
   <!-- Biometric FRR notification messages -->
   <java-symbol type="string" name="device_unlock_notification_name" />
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 64691e0..b2b6ee6 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -57,6 +57,7 @@
     @NonNull private final FaceManager mFaceManager;
     @NonNull private final FingerprintManager mFingerprintManager;
 
+    private final boolean mEnabled;
     private final float mThreshold;
     private final int mModality;
     private boolean mPersisterInitialized = false;
@@ -83,6 +84,7 @@
     public AuthenticationStatsCollector(@NonNull Context context, int modality,
             @NonNull BiometricNotification biometricNotification) {
         mContext = context;
+        mEnabled = context.getResources().getBoolean(R.bool.config_biometricFrrNotificationEnabled);
         mThreshold = context.getResources()
                 .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
         mUserAuthenticationStatsMap = new HashMap<>();
@@ -116,6 +118,11 @@
     /** Update total authentication and rejected attempts. */
     public void authenticate(int userId, boolean authenticated) {
 
+        // Don't collect data if the feature is disabled.
+        if (!mEnabled) {
+            return;
+        }
+
         // Don't collect data for single-modality devices or user has both biometrics enrolled.
         if (isSingleModalityDevice()
                 || (hasEnrolledFace(userId) && hasEnrolledFingerprint(userId))) {
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 fa6e7f6..ba77390 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -87,6 +87,8 @@
     public void setUp() {
 
         when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getBoolean(eq(R.bool.config_biometricFrrNotificationEnabled)))
+                .thenReturn(true);
         when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold),
                 anyInt(), anyInt())).thenReturn(FRR_THRESHOLD);
 
@@ -109,7 +111,6 @@
                 0 /* modality */, mBiometricNotification);
     }
 
-
     @Test
     public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
         // Assert that the user doesn't exist in the map initially.
@@ -341,4 +342,32 @@
         // Assert that notification count has been updated.
         assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1);
     }
+
+    @Test
+    public void authenticate_featureDisabled_mapMustNotBeUpdated() {
+        // Disable the feature.
+        when(mResources.getBoolean(eq(R.bool.config_biometricFrrNotificationEnabled)))
+                .thenReturn(false);
+        AuthenticationStatsCollector authenticationStatsCollector =
+                new AuthenticationStatsCollector(mContext, 0 /* modality */,
+                        mBiometricNotification);
+
+        authenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+                        400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        authenticationStatsCollector.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 hasn't been updated.
+        AuthenticationStats authenticationStats = authenticationStatsCollector
+                .getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400);
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f);
+    }
 }