diff options
| author | 2024-06-28 01:19:34 +0000 | |
|---|---|---|
| committer | 2024-06-28 01:21:46 +0000 | |
| commit | 4708292020397d8a0b427295b45f25d2bda433bd (patch) | |
| tree | 3aa8e10b9489440591d5de83ba68747a1c387ebe | |
| parent | a514bd3b33789b2a82e2e40539b272c4d69210e2 (diff) | |
Add mandatory biometric prompt to platform surfaces (3/N)
1. Power off
Flag: android.hardware.biometrics.Flags.MANDATORY_BIOMETRICS
Bug: 339910718
Test: atest GlobalActionsDialogLiteTest
Change-Id: Icd024c52b3e4ecc5a3800215e8e0181147b468ce
9 files changed, 125 insertions, 1 deletions
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7a166dac046e..57668e892193 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 0f54d89e49c0..9f9ef86e3580 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 66b47dae1b3e..c573cf4ad628 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 b37db16f5786..1b9a09d71b09 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 1e4fb4f15062..c1de3818ef8d 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.content.res.Resources; 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @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 e2cca3873bf7..b58eb49fc9cf 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 com.google.common.truth.Truth.assertThat; 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 static org.mockito.Mockito.when; 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.GlobalSettings; 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 @@ import java.util.concurrent.Executor; @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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { 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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { } @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 2a1687209aad..f5a2a218684d 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 @@ public class AuthService extends SystemService { 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 daaafcb61bc5..693a3e66f911 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 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> } 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 9cd3186f99f3..36a5cda5285f 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 @@ public class AuthServiceTest { 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(); |