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:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user