Merge "Add mandatory biometric prompt to platform surfaces (3/N)" into main
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fa93e76..e3f9187 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -722,6 +722,9 @@
     <!-- label for screenshot item in power menu [CHAR LIMIT=24]-->
     <string name="global_action_screenshot">Screenshot</string>
 
+    <!-- description for mandatory biometrics prompt -->
+    <string name="identity_check_biometric_prompt_description">This is needed since Identity Check is on</string>
+
     <!-- Take bug report menu title [CHAR LIMIT=30] -->
     <string name="bugreport_title">Bug report</string>
     <!-- Message in bugreport dialog describing what it does [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bdcf13c..6e5e106 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1918,6 +1918,7 @@
   <java-symbol type="string" name="global_action_voice_assist" />
   <java-symbol type="string" name="global_action_assist" />
   <java-symbol type="string" name="global_action_screenshot" />
+  <java-symbol type="string" name="identity_check_biometric_prompt_description" />
   <java-symbol type="string" name="invalidPuk" />
   <java-symbol type="string" name="lockscreen_carrier_default" />
   <java-symbol type="style" name="Animation.LockScreen" />
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 66b47da..c573cf4a 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -92,5 +92,6 @@
         <permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
         <permission name="android.permission.CONTROL_UI_TRACING" />
         <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
+        <permission name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
     </privapp-permissions>
 </permissions>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b37db16..1b9a09d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -373,6 +373,8 @@
     <!-- Listen to (dis-)connection of external displays and enable / disable them. -->
     <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
 
+    <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 1e4fb4f..c1de381 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -51,10 +51,14 @@
 import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
 import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -857,6 +861,29 @@
             if (ActivityManager.isUserAMonkey()) {
                 return;
             }
+            if (Flags.mandatoryBiometrics()
+                    && requestBiometricAuthenticationForMandatoryBiometrics()) {
+                launchBiometricPromptForMandatoryBiometrics(
+                        new BiometricPrompt.AuthenticationCallback() {
+                            @Override
+                            public void onAuthenticationError(int errorCode,
+                                    CharSequence errString) {
+                                super.onAuthenticationError(errorCode, errString);
+                            }
+
+                            @Override
+                            public void onAuthenticationSucceeded(
+                                    BiometricPrompt.AuthenticationResult result) {
+                                super.onAuthenticationSucceeded(result);
+                                shutDown();
+                            }
+                        });
+            } else {
+                shutDown();
+            }
+        }
+
+        private void shutDown() {
             mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS);
             // shutdown by making sure radio and power are handled accordingly.
             mWindowManagerFuncs.shutdown();
@@ -2261,6 +2288,35 @@
     }
 
     @VisibleForTesting
+    void launchBiometricPromptForMandatoryBiometrics(
+            BiometricPrompt.AuthenticationCallback authenticationCallback) {
+        final CancellationSignal cancellationSignal = new CancellationSignal();
+        final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(mContext)
+                .setAllowedAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)
+                .setUseDefaultTitle()
+                .setDescription(mContext.getString(
+                        R.string.identity_check_biometric_prompt_description))
+                .setNegativeButton(mContext.getString(R.string.cancel), mContext.getMainExecutor(),
+                        (dialog, which) -> cancellationSignal.cancel())
+                .setAllowBackgroundAuthentication(true)
+                .build();
+        biometricPrompt.authenticate(cancellationSignal, mContext.getMainExecutor(),
+                authenticationCallback);
+    }
+
+    private boolean requestBiometricAuthenticationForMandatoryBiometrics() {
+        final BiometricManager biometricManager =
+                (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
+        if (biometricManager == null) {
+            Log.e(TAG, "Biometric Manager is null.");
+            return false;
+        }
+        final int status = biometricManager.canAuthenticate(
+                BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+        return status == BiometricManager.BIOMETRIC_SUCCESS;
+    }
+
+    @VisibleForTesting
     static class ActionsDialogLite extends SystemUIDialog implements DialogInterface,
             ColorExtractor.OnColorsChangedListener {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e2cca38..b58eb49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -33,13 +34,20 @@
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
 import android.media.AudioManager;
 import android.os.Handler;
 import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.service.dreams.IDreamManager;
 import android.testing.TestableLooper;
@@ -88,6 +96,7 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -102,6 +111,9 @@
 @RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class GlobalActionsDialogLiteTest extends SysuiTestCase {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
     private GlobalActionsDialogLite mGlobalActionsDialogLite;
 
     @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
@@ -141,6 +153,7 @@
     @Mock private DialogTransitionAnimator mDialogTransitionAnimator;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    @Mock private BiometricManager mBiometricManager;
     @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
 
     private TestableLooper mTestableLooper;
@@ -157,10 +170,13 @@
         when(mUserContextProvider.getUserContext()).thenReturn(mContext);
         when(mResources.getConfiguration()).thenReturn(
                 getContext().getResources().getConfiguration());
+        when(mBiometricManager.canAuthenticate(anyInt())).thenReturn(
+                BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
 
         mGlobalSettings = new FakeGlobalSettings();
         mSecureSettings = new FakeSettings();
         mInteractor = mKosmos.getGlobalActionsInteractor();
+        mContext.addMockSystemService(Context.BIOMETRIC_SERVICE, mBiometricManager);
 
         mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
                 mWindowManagerFuncs,
@@ -551,6 +567,35 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void requestBiometricAuth_whenShutDownShortPressAndMandatoryBiometricsActive() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        ArgumentCaptor<BiometricPrompt.AuthenticationCallback>
+                authenticationCallbackArgumentCaptor = ArgumentCaptor.forClass(
+                        BiometricPrompt.AuthenticationCallback.class);
+
+        when(mBiometricManager.canAuthenticate(
+                BiometricManager.Authenticators.MANDATORY_BIOMETRICS)).thenReturn(
+                        BiometricManager.BIOMETRIC_SUCCESS);
+        doNothing().when(mGlobalActionsDialogLite).launchBiometricPromptForMandatoryBiometrics(
+                any());
+
+        GlobalActionsDialogLite.ShutDownAction shutDownAction =
+                mGlobalActionsDialogLite.new ShutDownAction();
+        shutDownAction.onPress();
+
+        verify(mGlobalActionsDialogLite).launchBiometricPromptForMandatoryBiometrics(
+                authenticationCallbackArgumentCaptor.capture());
+
+        BiometricPrompt.AuthenticationCallback authenticationCallback =
+                authenticationCallbackArgumentCaptor.getValue();
+        authenticationCallback.onAuthenticationSucceeded(null);
+
+        verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SHUTDOWN_PRESS);
+        verify(mWindowManagerFuncs).shutdown();
+    }
+
+    @Test
     public void testShouldLogLockdownPress() {
         GlobalActionsDialogLite.LockDownAction lockDownAction =
                 mGlobalActionsDialogLite.new LockDownAction();
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 2a16872..f5a2a21 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -369,6 +369,10 @@
                 checkPermission();
             }
 
+            if ((authenticators & Authenticators.MANDATORY_BIOMETRICS) != 0) {
+                checkBiometricAdvancedPermission();
+            }
+
             final long identity = Binder.clearCallingIdentity();
             try {
                 final int result = mBiometricService.canAuthenticate(
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index daaafcb..693a3e6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -418,7 +418,7 @@
     }
 
     protected int getRequestReason() {
-        if (isKeyguard()) {
+        if (isKeyguard() && !isBiometricPrompt()) {
             return BiometricRequestConstants.REASON_AUTH_KEYGUARD;
         } else if (isBiometricPrompt()) {
             // BP reason always takes precedent over settings, since callers from within
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 9cd3186..36a5cda 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -393,6 +393,18 @@
         testAuthenticate_throwsSecurityException(promptInfo);
     }
 
+    @Test
+    public void testCanAuthenticate_throwsWhenUsingAdvancedApis() {
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        assertThrows(SecurityException.class, () -> {
+            mAuthService.mImpl.canAuthenticate(TEST_OP_PACKAGE_NAME, 1 /* userId */,
+                    BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+            waitForIdle();
+        });
+    }
+
     private void testAuthenticate_throwsSecurityException(PromptInfo promptInfo) {
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();