Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8c9fe83 authored by Wenhui Yang's avatar Wenhui Yang
Browse files

Update bp subtitle base on modality

Change the subtile of biometric prompt to reflect modality.
Test: Manual (see bug)
Test: atest BiometricServiceTest
Fixes: 283151438

Change-Id: I290148e47cf6daf0821df1da10907124848266bc
parent 3b651858
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -1780,6 +1780,10 @@
    <string name="biometric_dialog_default_title">Verify it\u2019s you</string>
    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
    <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
    <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
    <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
    <string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>

+2 −0
Original line number Diff line number Diff line
@@ -2566,6 +2566,8 @@
  <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
  <java-symbol type="string" name="biometric_dialog_default_title" />
  <java-symbol type="string" name="biometric_dialog_default_subtitle" />
  <java-symbol type="string" name="biometric_dialog_face_subtitle" />
  <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
  <java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
  <java-symbol type="string" name="biometric_error_hw_unavailable" />
  <java-symbol type="string" name="biometric_error_user_canceled" />
+30 −17
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.server.biometrics;

import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;

import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -369,7 +371,7 @@ public class BiometricService extends SystemService {
        public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
                int userId) {
            switch (modality) {
                case BiometricAuthenticator.TYPE_FACE:
                case TYPE_FACE:
                    if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
                        onChange(true /* selfChange */,
                                FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
@@ -567,22 +569,6 @@ public class BiometricService extends SystemService {

            Utils.combineAuthenticatorBundles(promptInfo);

            // Set the default title if necessary.
            if (promptInfo.isUseDefaultTitle()) {
                if (TextUtils.isEmpty(promptInfo.getTitle())) {
                    promptInfo.setTitle(getContext()
                            .getString(R.string.biometric_dialog_default_title));
                }
            }

            // Set the default subtitle if necessary.
            if (promptInfo.isUseDefaultSubtitle()) {
                if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
                    promptInfo.setSubtitle(getContext()
                            .getString(R.string.biometric_dialog_default_subtitle));
                }
            }

            final long requestId = mRequestCounter.get();
            mHandler.post(() -> handleAuthenticate(
                    token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
@@ -1302,6 +1288,33 @@ public class BiometricService extends SystemService {
                        opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
                        getContext(), mBiometricCameraManager);

                // Set the default title if necessary.
                if (promptInfo.isUseDefaultTitle()) {
                    if (TextUtils.isEmpty(promptInfo.getTitle())) {
                        promptInfo.setTitle(getContext()
                                .getString(R.string.biometric_dialog_default_title));
                    }
                }

                final int eligible = preAuthInfo.getEligibleModalities();
                final boolean hasEligibleFingerprintSensor =
                        (eligible & TYPE_FINGERPRINT) == TYPE_FINGERPRINT;
                final boolean hasEligibleFaceSensor = (eligible & TYPE_FACE) == TYPE_FACE;

                // Set the subtitle according to the modality.
                if (promptInfo.isUseDefaultSubtitle()) {
                    if (hasEligibleFingerprintSensor && hasEligibleFaceSensor) {
                        promptInfo.setSubtitle(getContext()
                                .getString(R.string.biometric_dialog_default_subtitle));
                    } else if (hasEligibleFingerprintSensor) {
                        promptInfo.setSubtitle(getContext()
                                .getString(R.string.biometric_dialog_fingerprint_subtitle));
                    } else if (hasEligibleFaceSensor) {
                        promptInfo.setSubtitle(getContext()
                                .getString(R.string.biometric_dialog_face_subtitle));
                    }
                }

                final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();

                Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first
+84 −25
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.biometrics;

import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -113,6 +114,10 @@ public class BiometricServiceTest {
    private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process";
    private static final String ERROR_USER_CANCELED = "error_user_canceled";
    private static final String ERROR_LOCKOUT = "error_lockout";
    private static final String FACE_SUBTITLE = "face_subtitle";
    private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
    private static final String DEFAULT_SUBTITLE = "default_subtitle";


    private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";

@@ -191,6 +196,12 @@ public class BiometricServiceTest {
                .thenReturn(ERROR_NOT_RECOGNIZED);
        when(mResources.getString(R.string.biometric_error_user_canceled))
                .thenReturn(ERROR_USER_CANCELED);
        when(mContext.getString(R.string.biometric_dialog_face_subtitle))
                .thenReturn(FACE_SUBTITLE);
        when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
                .thenReturn(FINGERPRINT_SUBTITLE);
        when(mContext.getString(R.string.biometric_dialog_default_subtitle))
                .thenReturn(DEFAULT_SUBTITLE);

        when(mWindowManager.getDefaultDisplay()).thenReturn(
                new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -211,7 +222,7 @@ public class BiometricServiceTest {

    @Test
    public void testClientBinderDied_whenPaused() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);

        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                true /* requireConfirmation */, null /* authenticators */);
@@ -238,7 +249,7 @@ public class BiometricServiceTest {

    @Test
    public void testClientBinderDied_whenAuthenticating() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);

        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                true /* requireConfirmation */, null /* authenticators */);
@@ -374,7 +385,7 @@ public class BiometricServiceTest {

        final int[] modalities = new int[] {
                TYPE_FINGERPRINT,
                BiometricAuthenticator.TYPE_FACE,
                TYPE_FACE,
        };

        final int[] strengths = new int[] {
@@ -426,10 +437,57 @@ public class BiometricServiceTest {
                eq(0 /* vendorCode */));
    }

    @Test
    public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);

        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */,
                null);
        waitForIdle();

        assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
    }

    @Test
    public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);

        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */,
                null);
        waitForIdle();

        assertEquals(FINGERPRINT_SUBTITLE,
                mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
    }

    @Test
    public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
        final int[] modalities = new int[] {
                TYPE_FINGERPRINT,
                TYPE_FACE,
        };

        final int[] strengths = new int[] {
                Authenticators.BIOMETRIC_WEAK,
                Authenticators.BIOMETRIC_STRONG,
        };

        setupAuthForMultiple(modalities, strengths);

        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */,
                null);
        waitForIdle();

        assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
    }

    @Test
    public void testAuthenticateFace_respectsUserSetting()
            throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);

        // Disabled in user settings receives onError
        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
@@ -568,7 +626,7 @@ public class BiometricServiceTest {

    @Test
    public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                .thenReturn(true);
@@ -595,13 +653,13 @@ public class BiometricServiceTest {

    @Test
    public void testAuthenticate_happyPathWithConfirmation_strongBiometric() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        testAuthenticate_happyPathWithConfirmation(true /* isStrongBiometric */);
    }

    @Test
    public void testAuthenticate_happyPathWithConfirmation_weakBiometric() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
        testAuthenticate_happyPathWithConfirmation(false /* isStrongBiometric */);
    }

@@ -637,7 +695,7 @@ public class BiometricServiceTest {

    @Test
    public void testAuthenticate_no_Biometrics_noCredential() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                .thenReturn(false);
@@ -655,7 +713,7 @@ public class BiometricServiceTest {
    @Test
    public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
            throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

@@ -663,7 +721,7 @@ public class BiometricServiceTest {
        waitForIdle();

        verify(mBiometricService.mStatusBarService).onBiometricError(
                eq(BiometricAuthenticator.TYPE_FACE),
                eq(TYPE_FACE),
                eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
                eq(0 /* vendorCode */));
        verify(mReceiver1).onAuthenticationFailed();
@@ -691,7 +749,7 @@ public class BiometricServiceTest {

    @Test
    public void testRequestAuthentication_whenAlreadyAuthenticating() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

@@ -700,7 +758,7 @@ public class BiometricServiceTest {
        waitForIdle();

        verify(mReceiver1).onError(
                eq(BiometricAuthenticator.TYPE_FACE),
                eq(TYPE_FACE),
                eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
                eq(0) /* vendorCode */);
        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
@@ -710,7 +768,7 @@ public class BiometricServiceTest {

    @Test
    public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

@@ -723,7 +781,7 @@ public class BiometricServiceTest {

        assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
        verify(mBiometricService.mStatusBarService).onBiometricError(
                eq(BiometricAuthenticator.TYPE_FACE),
                eq(TYPE_FACE),
                eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
                eq(0 /* vendorCode */));
        // Timeout does not count as fail as per BiometricPrompt documentation.
@@ -759,7 +817,7 @@ public class BiometricServiceTest {

    @Test
    public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

@@ -777,7 +835,7 @@ public class BiometricServiceTest {

        // Client receives error immediately
        verify(mReceiver1).onError(
                eq(BiometricAuthenticator.TYPE_FACE),
                eq(TYPE_FACE),
                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                eq(0 /* vendorCode */));
        // Dialog is hidden immediately
@@ -926,7 +984,7 @@ public class BiometricServiceTest {
            int biometricPromptError) throws Exception {
        final int[] modalities = new int[] {
                TYPE_FINGERPRINT,
                BiometricAuthenticator.TYPE_FACE,
                TYPE_FACE,
        };

        final int[] strengths = new int[] {
@@ -1123,7 +1181,7 @@ public class BiometricServiceTest {

    @Test
    public void testDismissedReasonNegative_whilePaused_invokeHalCancel() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

@@ -1142,7 +1200,7 @@ public class BiometricServiceTest {

    @Test
    public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

@@ -1161,7 +1219,7 @@ public class BiometricServiceTest {

    @Test
    public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                true /* requireConfirmation */, null /* authenticators */);

@@ -1175,7 +1233,7 @@ public class BiometricServiceTest {
        verify(mBiometricService.mSensors.get(0).impl)
                .cancelAuthenticationFromService(any(), any(), anyLong());
        verify(mReceiver1).onError(
                eq(BiometricAuthenticator.TYPE_FACE),
                eq(TYPE_FACE),
                eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
                eq(0 /* vendorCode */));
        verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
@@ -1296,7 +1354,7 @@ public class BiometricServiceTest {

    @Test
    public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                .thenReturn(true);
@@ -1590,7 +1648,7 @@ public class BiometricServiceTest {
    @Test
    public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
            throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
        when(mDevicePolicyManager
                .getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */))
                .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -1683,7 +1741,7 @@ public class BiometricServiceTest {
                    mFingerprintAuthenticator);
        }

        if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
        if ((modality & TYPE_FACE) != 0) {
            when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
            when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
            when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
@@ -1715,7 +1773,7 @@ public class BiometricServiceTest {
                        strength, mFingerprintAuthenticator);
            }

            if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
            if ((modality & TYPE_FACE) != 0) {
                when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
                when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
                mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
@@ -1798,6 +1856,7 @@ public class BiometricServiceTest {
            boolean checkDevicePolicy) {
        final PromptInfo promptInfo = new PromptInfo();
        promptInfo.setConfirmationRequested(requireConfirmation);
        promptInfo.setUseDefaultSubtitle(true);

        if (authenticators != null) {
            promptInfo.setAuthenticators(authenticators);