Support force fingerprint reenrollment

Add a new fingerprint constant FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED and
rename existing FINGERPRINT_ACQUIRED_RE_ENROLL to
FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL.

When force reenroll is received, only show the positive button in the
dialog.

Bug: 325516143
Test: atest BiometricNotificationDialogFactoryTest
            BiometricNotificationServiceTest
Flag: NONE

Change-Id: Ie7c1bb0df144172c5a202fcd74f4b3715b57869b
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 770448b..fc72db3 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -221,7 +221,8 @@
             FINGERPRINT_ACQUIRED_IMMOBILE,
             FINGERPRINT_ACQUIRED_TOO_BRIGHT,
             FINGERPRINT_ACQUIRED_POWER_PRESSED,
-            FINGERPRINT_ACQUIRED_RE_ENROLL})
+            FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL,
+            FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED})
     @Retention(RetentionPolicy.SOURCE)
     @interface FingerprintAcquired {}
 
@@ -316,7 +317,13 @@
      * This message is sent to encourage the user to re-enroll their fingerprints.
      * @hide
      */
-    int FINGERPRINT_ACQUIRED_RE_ENROLL = 12;
+    int FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL = 12;
+
+    /**
+     * This message is sent to force the user to re-enroll their fingerprints.
+     * @hide
+     */
+    int FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED = 13;
 
     /**
      * @hide
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
index df27cbb..027f674 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
@@ -35,6 +35,8 @@
     static final String ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG =
             "fingerprint_action_show_reenroll_dialog";
 
+    static final String EXTRA_IS_REENROLL_FORCED = "is_reenroll_forced";
+
     private static final String TAG = "BiometricNotificationBroadcastReceiver";
 
     private final Context mContext;
@@ -56,14 +58,16 @@
                 mNotificationDialogFactory.createReenrollDialog(
                         mContext.getUserId(),
                         mContext::startActivity,
-                        BiometricSourceType.FACE)
+                        BiometricSourceType.FACE,
+                        false)
                         .show();
                 break;
             case ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG:
                 mNotificationDialogFactory.createReenrollDialog(
                         mContext.getUserId(),
                         mContext::startActivity,
-                        BiometricSourceType.FINGERPRINT)
+                        BiometricSourceType.FINGERPRINT,
+                        intent.getBooleanExtra(EXTRA_IS_REENROLL_FORCED, false))
                         .show();
                 break;
             default:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
index fd0feef..4ac5a12 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
@@ -29,13 +29,12 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 /**
  * Manages the creation of dialogs to be shown for biometric re enroll notifications.
@@ -61,7 +60,8 @@
     }
 
     Dialog createReenrollDialog(
-            int userId, ActivityStarter activityStarter, BiometricSourceType biometricSourceType) {
+            int userId, ActivityStarter activityStarter, BiometricSourceType biometricSourceType,
+            boolean isReenrollForced) {
         SystemUIDialog sysuiDialog = mSystemUIDialogFactory.create();
         if (biometricSourceType == BiometricSourceType.FACE) {
             sysuiDialog.setTitle(mResources.getString(R.string.face_re_enroll_dialog_title));
@@ -80,8 +80,12 @@
         sysuiDialog.setPositiveButton(R.string.biometric_re_enroll_dialog_confirm,
                 (dialog, which) -> onReenrollDialogConfirm(
                         userId, biometricSourceType, activityStarter));
-        sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel,
-                (dialog, which) -> {});
+        if (!isReenrollForced) {
+            sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel,
+                    (dialog, which) -> {
+                    });
+        }
+        sysuiDialog.setCanceledOnTouchOutside(!isReenrollForced);
         return sysuiDialog;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index d6a4cbb..3b49ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics;
 
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 
 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
@@ -43,8 +44,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.Optional;
@@ -80,6 +81,8 @@
     private boolean mFingerprintNotificationQueued;
     private boolean mFingerprintReenrollRequired;
 
+    private boolean mIsFingerprintReenrollForced;
+
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
             new KeyguardStateController.Callback() {
                 private boolean mIsShowing = true;
@@ -118,9 +121,11 @@
                 public void onBiometricHelp(int msgId, String helpString,
                         BiometricSourceType biometricSourceType) {
                     if (biometricSourceType == BiometricSourceType.FINGERPRINT
-                            && mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+                            && mFingerprintReEnrollNotification.isFingerprintReEnrollRequested(
                                     msgId)) {
                         mFingerprintReenrollRequired = true;
+                        mIsFingerprintReenrollForced =
+                                mFingerprintReEnrollNotification.isFingerprintReEnrollForced(msgId);
                     }
                 }
             };
@@ -191,7 +196,7 @@
         final String name = mContext.getString(R.string.face_re_enroll_notification_name);
         mHandler.postDelayed(
                 () -> showNotification(ACTION_SHOW_FACE_REENROLL_DIALOG, title, content, name,
-                        FACE_NOTIFICATION_ID),
+                        FACE_NOTIFICATION_ID, false),
                 SHOW_NOTIFICATION_DELAY_MS);
     }
 
@@ -204,12 +209,12 @@
         final String name = mContext.getString(R.string.fingerprint_re_enroll_notification_name);
         mHandler.postDelayed(
                 () -> showNotification(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG, title, content,
-                        name, FINGERPRINT_NOTIFICATION_ID),
+                        name, FINGERPRINT_NOTIFICATION_ID, mIsFingerprintReenrollForced),
                 SHOW_NOTIFICATION_DELAY_MS);
     }
 
     private void showNotification(String action, CharSequence title, CharSequence content,
-            CharSequence name, int notificationId) {
+            CharSequence name, int notificationId, boolean isReenrollForced) {
         if (notificationId == FACE_NOTIFICATION_ID) {
             mFaceNotificationQueued = false;
         } else if (notificationId == FINGERPRINT_NOTIFICATION_ID) {
@@ -223,8 +228,12 @@
         }
 
         final Intent onClickIntent = new Intent(action);
+        onClickIntent.putExtra(BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED,
+                isReenrollForced);
+
         final PendingIntent onClickPendingIntent = PendingIntent.getBroadcastAsUser(mContext,
-                0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE, UserHandle.CURRENT);
+                0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT,
+                UserHandle.CURRENT);
 
         final Notification notification = new Notification.Builder(mContext, CHANNEL_ID)
                 .setCategory(Notification.CATEGORY_SYSTEM)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
index 9050f26..5b9ed483 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
@@ -23,7 +23,16 @@
  */
 public interface FingerprintReEnrollNotification {
     //TODO: Remove this class and add a constant in the HAL API instead (b/281841852)
-    /** Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL. */
-    boolean isFingerprintReEnrollRequired(
+    /**
+     * Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL or
+     * FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED.
+     */
+    boolean isFingerprintReEnrollRequested(
+            @BiometricFingerprintConstants.FingerprintAcquired int msgId);
+
+    /**
+     * Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED.
+     */
+    boolean isFingerprintReEnrollForced(
             @BiometricFingerprintConstants.FingerprintAcquired int msgId);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
index 1f86bc6..d47e1e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
@@ -23,7 +23,13 @@
  */
 public class FingerprintReEnrollNotificationImpl implements FingerprintReEnrollNotification{
     @Override
-    public boolean isFingerprintReEnrollRequired(int msgId) {
-        return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL;
+    public boolean isFingerprintReEnrollRequested(int msgId) {
+        return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_OPTIONAL
+                || msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED;
+    }
+
+    @Override
+    public boolean isFingerprintReEnrollForced(int msgId) {
+        return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL_FORCED;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
index 8c8544c..d2c6957 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -90,7 +91,8 @@
         assumeTrue(getContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
 
-        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT);
+        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+                false);
 
         verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
 
@@ -115,7 +117,8 @@
         assumeTrue(getContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
 
-        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT);
+        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+                false);
 
         verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
 
@@ -134,11 +137,25 @@
     }
 
     @Test
+    public void testFingerprintReEnrollDialog_forced() {
+        assumeTrue(getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
+
+        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT,
+                true);
+
+        verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
+
+        verify(mDialog, never()).setNegativeButton(anyInt(), any());
+    }
+
+    @Test
     public void testFaceReEnrollDialog_onRemovalSucceeded() {
         assumeTrue(getContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_FACE));
 
-        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE);
+        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE,
+                false);
 
         verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
 
@@ -163,7 +180,8 @@
         assumeTrue(getContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_FACE));
 
-        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE);
+        mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE,
+                false);
 
         verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index c6771b2..a279d3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -18,7 +18,9 @@
 
 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -98,7 +100,7 @@
     public void setUp() {
         when(mFingerprintReEnrollNotificationOptional.orElse(any()))
                 .thenReturn(mFingerprintReEnrollNotification);
-        when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+        when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequested(
                 FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
 
         mLooper = TestableLooper.get(this);
@@ -140,8 +142,10 @@
     }
 
     @Test
-    public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() {
+    public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll_Optional() {
         when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mFingerprintReEnrollNotification.isFingerprintReEnrollForced(
+                FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(false);
 
         mKeyguardUpdateMonitorCallback.onBiometricHelp(
                 FINGERPRINT_ACQUIRED_RE_ENROLL,
@@ -160,6 +164,35 @@
 
         assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
                 .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+        assertThat(fingerprintNotification.contentIntent.getIntent().getBooleanExtra(
+                BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED, false)).isFalse();
+    }
+
+    @Test
+    public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll_force() {
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mFingerprintReEnrollNotification.isFingerprintReEnrollForced(
+                FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
+
+        mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                FINGERPRINT_ACQUIRED_RE_ENROLL,
+                "Testing Fingerprint Re-enrollment" /* errString */,
+                BiometricSourceType.FINGERPRINT
+        );
+        mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+        mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+        mLooper.processAllMessages();
+
+        verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+                mNotificationArgumentCaptor.capture(), any());
+
+        Notification fingerprintNotification = mNotificationArgumentCaptor.getValue();
+
+        assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
+                .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+        assertThat(fingerprintNotification.contentIntent.getIntent().getBooleanExtra(
+                BiometricNotificationBroadcastReceiver.EXTRA_IS_REENROLL_FORCED, false)).isTrue();
     }
     @Test
     public void testShowFaceReEnrollNotification_onErrorReEnroll() {