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
This commit is contained in:
Kevin Chyn
2019-10-03 15:32:37 -07:00
parent 8110ebb57c
commit c70d6b83e8
5 changed files with 126 additions and 67 deletions

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;
// Check for errors, notify callback, and return
if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
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
try {
final String hardwareUnavailable =
getContext().getString(R.string.biometric_error_hw_unavailable);
@@ -1450,27 +1455,49 @@ 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);
mPendingAuthSession.mState = STATE_AUTH_CALLED;
// No polymorphism :(
if ((modality & TYPE_FINGERPRINT) != 0) {
mFingerprintService.prepareForAuthentication(token, sessionId, userId,
mInternalReceiver, opPackageName, cookie,
callingUid, callingPid, callingUserId);
}
if ((modality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((modality & TYPE_FACE) != 0) {
mFaceService.prepareForAuthentication(requireConfirmation,
token, sessionId, userId, mInternalReceiver, opPackageName,
cookie, callingUid, callingPid, callingUserId);
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) {
mFingerprintService.prepareForAuthentication(token, sessionId, userId,
mInternalReceiver, opPackageName, cookie,
callingUid, callingPid, callingUserId);
}
if ((modality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((modality & TYPE_FACE) != 0) {
mFaceService.prepareForAuthentication(requireConfirmation,
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);
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}