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

Commit c70d6b83 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

17/n: Show credential UI if setDeviceCredentialAllowed(true) and no biometrics

Also, get credential type after userId is set. Otherwise the UI is
incorrect.

Bug: 140127687

Test: atest BiometricServiceTest
Test: manual test with managed profile, one-lock disabled, with/without
      fingerprint, and with different types of credentials between
      owner and managed profile

Change-Id: Ibaf537acf6190458d093a404d9b20d279937a6cc
parent 8110ebb5
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -51,12 +51,6 @@ public class AuthCredentialPasswordView extends AuthCredentialView
        super.onFinishInflate();
        mPasswordField = findViewById(R.id.lockPassword);
        mPasswordField.setOnEditorActionListener(this);

        if (mCredentialType == Utils.CREDENTIAL_PIN) {
            mPasswordField.setInputType(
                    InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
        }

        mPasswordField.setOnKeyListener((v, keyCode, event) -> {
            if (keyCode != KeyEvent.KEYCODE_BACK) {
                return false;
@@ -72,6 +66,11 @@ public class AuthCredentialPasswordView extends AuthCredentialView
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (mCredentialType == Utils.CREDENTIAL_PIN) {
            mPasswordField.setInputType(
                    InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
        }

        // Wait a bit to focus the field so the focusable flag on the window is already set then.
        post(() -> {
            mPasswordField.requestFocus();
+2 −1
Original line number Diff line number Diff line
@@ -166,6 +166,8 @@ public abstract class AuthCredentialView extends LinearLayout {
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        mCredentialType = Utils.getCredentialType(mContext, mUserId);

        setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE));
        setTextOrHide(mSubtitleView,
                mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE));
@@ -200,7 +202,6 @@ public abstract class AuthCredentialView extends LinearLayout {
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mCredentialType = Utils.getCredentialType(mContext, mUserId);
        mTitleView = findViewById(R.id.title);
        mSubtitleView = findViewById(R.id.subtitle);
        mDescriptionView = findViewById(R.id.description);
+50 −57
Original line number Diff line number Diff line
@@ -212,8 +212,7 @@ public class BiometricService extends SystemService {
        }

        boolean isAllowDeviceCredential() {
            final int authenticators = mBundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
            return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
            return Utils.isDeviceCredentialAllowed(mBundle);
        }
    }

@@ -613,7 +612,7 @@ public class BiometricService extends SystemService {
                checkInternalPermission();
            }

            combineAuthenticatorBundles(bundle);
            Utils.combineAuthenticatorBundles(bundle);

            // Check the usage of this in system server. Need to remove this check if it becomes
            // a public API.
@@ -1392,8 +1391,14 @@ public class BiometricService extends SystemService {
            final int modality = result.first;
            final int error = result.second;

            final boolean credentialAllowed = Utils.isDeviceCredentialAllowed(bundle);

            if (error != BiometricConstants.BIOMETRIC_SUCCESS && credentialAllowed) {
                // If there's a problem but device credential is allowed, only show credential UI.
                bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
                        Authenticator.TYPE_CREDENTIAL);
            } else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
                // Check for errors, notify callback, and return
            if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
                try {
                    final String hardwareUnavailable =
                            getContext().getString(R.string.biometric_error_hw_unavailable);
@@ -1450,13 +1455,34 @@ public class BiometricService extends SystemService {
            // with the cookie. Once all cookies are received, we can show the prompt
            // and let the services start authenticating. The cookie should be non-zero.
            final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
            final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
            Slog.d(TAG, "Creating auth session. Modality: " + modality
                    + ", cookie: " + cookie);
            final HashMap<Integer, Integer> authenticators = new HashMap<>();
            authenticators.put(modality, cookie);
            mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId,
                    + ", cookie: " + cookie
                    + ", authenticators: " + authenticators);
            final HashMap<Integer, Integer> modalities = new HashMap<>();

            // If it's only device credential, we don't need to wait - LockSettingsService is
            // always ready to check credential (SystemUI invokes that path).
            if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
                modalities.put(modality, cookie);
            }
            mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
                    receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
                    modality, requireConfirmation);

            if (authenticators == Authenticator.TYPE_CREDENTIAL) {
                mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                mCurrentAuthSession = mPendingAuthSession;
                mPendingAuthSession = null;

                mStatusBarService.showAuthenticationDialog(
                        mCurrentAuthSession.mBundle,
                        mInternalReceiver,
                        0 /* biometricModality */,
                        false /* requireConfirmation */,
                        mCurrentAuthSession.mUserId,
                        mCurrentAuthSession.mOpPackageName);
            } else {
                mPendingAuthSession.mState = STATE_AUTH_CALLED;
                // No polymorphism :(
                if ((modality & TYPE_FINGERPRINT) != 0) {
@@ -1472,6 +1498,7 @@ public class BiometricService extends SystemService {
                            token, sessionId, userId, mInternalReceiver, opPackageName,
                            cookie, callingUid, callingPid, callingUserId);
                }
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "Unable to start authentication", e);
        }
@@ -1536,38 +1563,4 @@ public class BiometricService extends SystemService {
            Slog.e(TAG, "Unable to cancel authentication");
        }
    }


    /**
     * Combine {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
     * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible
     * enough.
     */
    @VisibleForTesting
    static void combineAuthenticatorBundles(Bundle bundle) {
        boolean biometricEnabled = true; // enabled by default
        boolean credentialEnabled = false; // disabled by default
        if (bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false)) {
            credentialEnabled = true;
        }
        if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
            final int authenticatorFlags =
                    bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
            biometricEnabled = (authenticatorFlags & Authenticator.TYPE_BIOMETRIC) != 0;
            // Using both KEY_ALLOW_DEVICE_CREDENTIAL and KEY_AUTHENTICATORS_ALLOWED together
            // is not supported. Default to overwriting.
            credentialEnabled = (authenticatorFlags & Authenticator.TYPE_CREDENTIAL) != 0;
        }

        bundle.remove(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);

        int authenticators = 0;
        if (biometricEnabled) {
            authenticators |= Authenticator.TYPE_BIOMETRIC;
        }
        if (credentialEnabled) {
            authenticators |= Authenticator.TYPE_CREDENTIAL;
        }
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
    }
}
+44 −0
Original line number Diff line number Diff line
@@ -17,10 +17,15 @@
package com.android.server.biometrics;

import android.content.Context;
import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;

import com.android.internal.annotations.VisibleForTesting;

public class Utils {
    public static boolean isDebugEnabled(Context context, int targetUserId) {
        if (targetUserId == UserHandle.USER_NULL) {
@@ -38,4 +43,43 @@ public class Utils {
        }
        return true;
    }

    /**
     * Combine {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
     * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible
     * enough.
     */
    public static void combineAuthenticatorBundles(Bundle bundle) {
        boolean biometricEnabled = true; // enabled by default
        boolean credentialEnabled = bundle.getBoolean(
                BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
        if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
            final int authenticatorFlags =
                    bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
            biometricEnabled = (authenticatorFlags & Authenticator.TYPE_BIOMETRIC) != 0;
            // Using both KEY_ALLOW_DEVICE_CREDENTIAL and KEY_AUTHENTICATORS_ALLOWED together
            // is not supported. Default to overwriting.
            credentialEnabled = (authenticatorFlags & Authenticator.TYPE_CREDENTIAL) != 0;
        }

        bundle.remove(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);

        int authenticators = 0;
        if (biometricEnabled) {
            authenticators |= Authenticator.TYPE_BIOMETRIC;
        }
        if (credentialEnabled) {
            authenticators |= Authenticator.TYPE_CREDENTIAL;
        }
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
    }

    /**
     * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
     * @return true if device credential allowed.
     */
    public static boolean isDeviceCredentialAllowed(Bundle bundle) {
        final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
        return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
    }
}
+25 −3
Original line number Diff line number Diff line
@@ -352,6 +352,28 @@ public class BiometricServiceTest {
        assertNull(mBiometricService.mCurrentAuthSession);
    }

    @Test
    public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                true /* requireConfirmation */, true /* allowDeviceCredential */);
        waitForIdle();

        assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
                mBiometricService.mCurrentAuthSession.mState);
        assertEquals(Authenticator.TYPE_CREDENTIAL,
                mBiometricService.mCurrentAuthSession.mBundle
                        .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
        verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
                eq(mBiometricService.mCurrentAuthSession.mBundle),
                any(IBiometricServiceReceiverInternal.class),
                eq(0 /* biometricModality */),
                anyBoolean() /* requireConfirmation */,
                anyInt() /* userId */,
                eq(TEST_PACKAGE_NAME));
    }

    @Test
    public void testAuthenticate_happyPathWithConfirmation() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
@@ -624,7 +646,7 @@ public class BiometricServiceTest {
        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
        authenticators = Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC;
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
        BiometricService.combineAuthenticatorBundles(bundle);
        Utils.combineAuthenticatorBundles(bundle);
        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
        assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));

@@ -638,7 +660,7 @@ public class BiometricServiceTest {
        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
        authenticators = Authenticator.TYPE_BIOMETRIC;
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
        BiometricService.combineAuthenticatorBundles(bundle);
        Utils.combineAuthenticatorBundles(bundle);
        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
        assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));

@@ -651,7 +673,7 @@ public class BiometricServiceTest {
        bundle = new Bundle();
        authenticators = Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL;
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
        BiometricService.combineAuthenticatorBundles(bundle);
        Utils.combineAuthenticatorBundles(bundle);
        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
        assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
    }