blob: db17f8397ed41a2ec3f755d90ec01ec2bcf00620 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.IActivityTaskManager;
import android.app.KeyguardManager;
import android.app.TaskStackListener;
import android.app.UserSwitchObserver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.FaceManager;
import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintService;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.StatsLog;
import com.android.internal.R;
import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
*/
public class BiometricService extends SystemService {
private static final String TAG = "BiometricService";
private static final boolean DEBUG = true;
private static final int MSG_ON_TASK_STACK_CHANGED = 1;
private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
private static final int MSG_ON_AUTHENTICATION_FAILED = 3;
private static final int MSG_ON_ERROR = 4;
private static final int MSG_ON_ACQUIRED = 5;
private static final int MSG_ON_DISMISSED = 6;
private static final int MSG_ON_TRY_AGAIN_PRESSED = 7;
private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8;
private static final int MSG_AUTHENTICATE = 9;
private static final int MSG_CANCEL_AUTHENTICATION = 10;
private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11;
private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12;
private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13;
private static final int[] FEATURE_ID = {
TYPE_FINGERPRINT,
TYPE_IRIS,
TYPE_FACE
};
/**
* Authentication either just called and we have not transitioned to the CALLED state, or
* authentication terminated (success or error).
*/
private static final int STATE_AUTH_IDLE = 0;
/**
* Authentication was called and we are waiting for the <Biometric>Services to return their
* cookies before starting the hardware and showing the BiometricPrompt.
*/
private static final int STATE_AUTH_CALLED = 1;
/**
* Authentication started, BiometricPrompt is showing and the hardware is authenticating.
*/
private static final int STATE_AUTH_STARTED = 2;
/**
* Authentication is paused, waiting for the user to press "try again" button. Only
* passive modalities such as Face or Iris should have this state. Note that for passive
* modalities, the HAL enters the idle state after onAuthenticated(false) which differs from
* fingerprint.
*/
private static final int STATE_AUTH_PAUSED = 3;
/**
* Authentication is successful, but we're waiting for the user to press "confirm" button.
*/
private static final int STATE_AUTH_PENDING_CONFIRM = 5;
/**
* Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential
*/
private static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6;
private final class AuthSession implements IBinder.DeathRecipient {
// Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
// <Biometric>Services before we can start authenticating. Pairs that have been returned
// are moved to mModalitiesMatched.
final HashMap<Integer, Integer> mModalitiesWaiting;
// Pairs that have been matched.
final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>();
// The following variables are passed to authenticateInternal, which initiates the
// appropriate <Biometric>Services.
final IBinder mToken;
final long mSessionId;
final int mUserId;
// Original receiver from BiometricPrompt.
final IBiometricServiceReceiver mClientReceiver;
final String mOpPackageName;
// Info to be shown on BiometricDialog when all cookies are returned.
final Bundle mBundle;
final int mCallingUid;
final int mCallingPid;
final int mCallingUserId;
// Continue authentication with the same modality/modalities after "try again" is
// pressed
final int mModality;
final boolean mRequireConfirmation;
// The current state, which can be either idle, called, or started
private int mState = STATE_AUTH_IDLE;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
byte[] mTokenEscrow;
// Timestamp when hardware authentication occurred
private long mAuthenticatedTimeMs;
// TODO(b/123378871): Remove when moved.
private IBiometricConfirmDeviceCredentialCallback mConfirmDeviceCredentialCallback;
AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
int userId, IBiometricServiceReceiver receiver, String opPackageName,
Bundle bundle, int callingUid, int callingPid, int callingUserId,
int modality, boolean requireConfirmation,
IBiometricConfirmDeviceCredentialCallback callback) {
mModalitiesWaiting = modalities;
mToken = token;
mSessionId = sessionId;
mUserId = userId;
mClientReceiver = receiver;
mOpPackageName = opPackageName;
mBundle = bundle;
mCallingUid = callingUid;
mCallingPid = callingPid;
mCallingUserId = callingUserId;
mModality = modality;
mRequireConfirmation = requireConfirmation;
mConfirmDeviceCredentialCallback = callback;
if (isFromConfirmDeviceCredential()) {
try {
token.linkToDeath(this, 0 /* flags */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to link to death", e);
}
}
}
boolean isCrypto() {
return mSessionId != 0;
}
boolean isFromConfirmDeviceCredential() {
return mBundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
}
boolean containsCookie(int cookie) {
if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
return true;
}
if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) {
return true;
}
return false;
}
// TODO(b/123378871): Remove when moved.
@Override
public void binderDied() {
mHandler.post(() -> {
Slog.e(TAG, "Binder died, killing ConfirmDeviceCredential");
if (mConfirmDeviceCredentialCallback == null) {
Slog.e(TAG, "Callback is null");
return;
}
try {
mConfirmDeviceCredentialCallback.cancel();
mConfirmDeviceCredentialCallback = null;
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send cancel", e);
}
});
}
}
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
mHandler.sendEmptyMessage(MSG_ON_TASK_STACK_CHANGED);
}
}
private final AppOpsManager mAppOps;
private final boolean mHasFeatureFingerprint;
private final boolean mHasFeatureIris;
private final boolean mHasFeatureFace;
private final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener();
private final Random mRandom = new Random();
private IFingerprintService mFingerprintService;
private IFaceService mFaceService;
private IActivityTaskManager mActivityTaskManager;
private IStatusBarService mStatusBarService;
// Get and cache the available authenticator (manager) classes. Used since aidl doesn't support
// polymorphism :/
final ArrayList<Authenticator> mAuthenticators = new ArrayList<>();
// Cache the current service that's being used. This is the service which
// cancelAuthentication() must be forwarded to. This is just a cache, and the actual
// check (is caller the current client) is done in the <Biometric>Service.
// Since Settings/System (not application) is responsible for changing preference, this
// should be safe.
private int mCurrentModality;
// The current authentication session, null if idle/done. We need to track both the current
// and pending sessions since errors may be sent to either.
private AuthSession mCurrentAuthSession;
private AuthSession mPendingAuthSession;
// TODO(b/123378871): Remove when moved.
// When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the
// client (app) receiver. BiometricService internally launches CDCA which invokes
// BiometricService to start authentication (normal path). When auth is success/rejected,
// CDCA will use an aidl method to poke BiometricService - the result will then be forwarded
// to this receiver.
private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ON_TASK_STACK_CHANGED: {
handleTaskStackChanged();
break;
}
case MSG_ON_AUTHENTICATION_SUCCEEDED: {
SomeArgs args = (SomeArgs) msg.obj;
handleAuthenticationSucceeded(
(boolean) args.arg1 /* requireConfirmation */,
(byte[]) args.arg2 /* token */);
args.recycle();
break;
}
case MSG_ON_AUTHENTICATION_FAILED: {
handleAuthenticationFailed((String) msg.obj /* failureReason */);
break;
}
case MSG_ON_ERROR: {
SomeArgs args = (SomeArgs) msg.obj;
handleOnError(
args.argi1 /* cookie */,
args.argi2 /* error */,
(String) args.arg1 /* message */);
args.recycle();
break;
}
case MSG_ON_ACQUIRED: {
SomeArgs args = (SomeArgs) msg.obj;
handleOnAcquired(
args.argi1 /* acquiredInfo */,
(String) args.arg1 /* message */);
args.recycle();
break;
}
case MSG_ON_DISMISSED: {
handleOnDismissed(msg.arg1);
break;
}
case MSG_ON_TRY_AGAIN_PRESSED: {
handleOnTryAgainPressed();
break;
}
case MSG_ON_READY_FOR_AUTHENTICATION: {
SomeArgs args = (SomeArgs) msg.obj;
handleOnReadyForAuthentication(
args.argi1 /* cookie */,
(boolean) args.arg1 /* requireConfirmation */,
args.argi2 /* userId */);
args.recycle();
break;
}
case MSG_AUTHENTICATE: {
SomeArgs args = (SomeArgs) msg.obj;
handleAuthenticate(
(IBinder) args.arg1 /* token */,
(long) args.arg2 /* sessionId */,
args.argi1 /* userid */,
(IBiometricServiceReceiver) args.arg3 /* receiver */,
(String) args.arg4 /* opPackageName */,
(Bundle) args.arg5 /* bundle */,
args.argi2 /* callingUid */,
args.argi3 /* callingPid */,
args.argi4 /* callingUserId */,
(IBiometricConfirmDeviceCredentialCallback) args.arg6 /* callback */);
args.recycle();
break;
}
case MSG_CANCEL_AUTHENTICATION: {
SomeArgs args = (SomeArgs) msg.obj;
handleCancelAuthentication(
(IBinder) args.arg1 /* token */,
(String) args.arg2 /* opPackageName */);
args.recycle();
break;
}
case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS: {
handleOnConfirmDeviceCredentialSuccess();
break;
}
case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR: {
SomeArgs args = (SomeArgs) msg.obj;
handleOnConfirmDeviceCredentialError(
args.argi1 /* error */,
(String) args.arg1 /* errorMsg */);
args.recycle();
break;
}
case MSG_REGISTER_CANCELLATION_CALLBACK: {
handleRegisterCancellationCallback(
(IBiometricConfirmDeviceCredentialCallback) msg.obj /* callback */);
break;
}
default:
Slog.e(TAG, "Unknown message: " + msg);
break;
}
}
};
private final class Authenticator {
int mType;
BiometricAuthenticator mAuthenticator;
Authenticator(int type, BiometricAuthenticator authenticator) {
mType = type;
mAuthenticator = authenticator;
}
int getType() {
return mType;
}
BiometricAuthenticator getAuthenticator() {
return mAuthenticator;
}
}
private final class SettingObserver extends ContentObserver {
private static final boolean DEFAULT_KEYGUARD_ENABLED = true;
private static final boolean DEFAULT_APP_ENABLED = true;
private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false;
private final Uri FACE_UNLOCK_KEYGUARD_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
private final Uri FACE_UNLOCK_APP_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED);
private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
private final ContentResolver mContentResolver;
private Map<Integer, Boolean> mFaceEnabledOnKeyguard = new HashMap<>();
private Map<Integer, Boolean> mFaceEnabledForApps = new HashMap<>();
private Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
SettingObserver(Handler handler) {
super(handler);
mContentResolver = getContext().getContentResolver();
updateContentObserver();
}
void updateContentObserver() {
mContentResolver.unregisterContentObserver(this);
mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
false /* notifyForDescendents */,
this /* observer */,
UserHandle.USER_ALL);
mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED,
false /* notifyForDescendents */,
this /* observer */,
UserHandle.USER_ALL);
mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
false /* notifyForDescendents */,
this /* observer */,
UserHandle.USER_ALL);
}
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) {
mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
mContentResolver,
Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED,
DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
userId) != 0);
if (userId == ActivityManager.getCurrentUser() && !selfChange) {
notifyEnabledOnKeyguardCallbacks(userId);
}
} else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) {
mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser(
mContentResolver,
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
userId) != 0);
} else if (FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION.equals(uri)) {
mFaceAlwaysRequireConfirmation.put(userId, Settings.Secure.getIntForUser(
mContentResolver,
Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
DEFAULT_ALWAYS_REQUIRE_CONFIRMATION ? 1 : 0 /* default */,
userId) != 0);
}
}
boolean getFaceEnabledOnKeyguard() {
final int user = ActivityManager.getCurrentUser();
if (!mFaceEnabledOnKeyguard.containsKey(user)) {
onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, user);
}
return mFaceEnabledOnKeyguard.get(user);
}
boolean getFaceEnabledForApps(int userId) {
if (!mFaceEnabledForApps.containsKey(userId)) {
onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
}
return mFaceEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
}
boolean getFaceAlwaysRequireConfirmation(int userId) {
if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, userId);
}
return mFaceAlwaysRequireConfirmation.get(userId);
}
void notifyEnabledOnKeyguardCallbacks(int userId) {
List<EnabledOnKeyguardCallback> callbacks = mEnabledOnKeyguardCallbacks;
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notify(BiometricSourceType.FACE,
mFaceEnabledOnKeyguard.getOrDefault(userId,
DEFAULT_KEYGUARD_ENABLED));
}
}
}
private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
private final IBiometricEnabledOnKeyguardCallback mCallback;
EnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) {
mCallback = callback;
try {
mCallback.asBinder().linkToDeath(EnabledOnKeyguardCallback.this, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to linkToDeath", e);
}
}
void notify(BiometricSourceType sourceType, boolean enabled) {
try {
mCallback.onChanged(sourceType, enabled);
} catch (DeadObjectException e) {
Slog.w(TAG, "Death while invoking notify", e);
mEnabledOnKeyguardCallbacks.remove(this);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke onChanged", e);
}
}
@Override
public void binderDied() {
Slog.e(TAG, "Enabled callback binder died");
mEnabledOnKeyguardCallbacks.remove(this);
}
}
// Wrap the client's receiver so we can do things with the BiometricDialog first
private final IBiometricServiceReceiverInternal mInternalReceiver =
new IBiometricServiceReceiverInternal.Stub() {
@Override
public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token)
throws RemoteException {
SomeArgs args = SomeArgs.obtain();
args.arg1 = requireConfirmation;
args.arg2 = token;
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget();
}
@Override
public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
throws RemoteException {
String failureReason = getContext().getString(R.string.biometric_not_recognized);
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, failureReason).sendToTarget();
}
@Override
public void onError(int cookie, int error, String message) throws RemoteException {
// Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
// soft errors and we should allow the user to try authenticating again instead of
// dismissing BiometricPrompt.
if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, message).sendToTarget();
} else {
SomeArgs args = SomeArgs.obtain();
args.argi1 = cookie;
args.argi2 = error;
args.arg1 = message;
mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
}
}
@Override
public void onAcquired(int acquiredInfo, String message) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
args.argi1 = acquiredInfo;
args.arg1 = message;
mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget();
}
@Override
public void onDialogDismissed(int reason) throws RemoteException {
mHandler.obtainMessage(MSG_ON_DISMISSED, reason, 0 /* arg2 */).sendToTarget();
}
@Override
public void onTryAgainPressed() {
mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED);
}
};
/**
* This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service
* should not carry any state. The reality is we need to keep a tiny amount of state so that
* cancelAuthentication() can go to the right place.
*/
private final class BiometricServiceWrapper extends IBiometricService.Stub {
@Override // Binder call
public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) {
checkInternalPermission();
SomeArgs args = SomeArgs.obtain();
args.argi1 = cookie;
args.arg1 = requireConfirmation;
args.argi2 = userId;
mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget();
}
@Override // Binder call
public void authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
IBiometricConfirmDeviceCredentialCallback callback)
throws RemoteException {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
// TODO(b/123378871): Remove when moved.
if (callback != null) {
checkInternalPermission();
}
// In the BiometricServiceBase, check do the AppOps and foreground check.
if (userId == callingUserId) {
// Check the USE_BIOMETRIC permission here.
checkPermission();
} else {
// Only allow internal clients to authenticate with a different userId
Slog.w(TAG, "User " + callingUserId + " is requesting authentication of userid: "
+ userId);
checkInternalPermission();
}
if (token == null || receiver == null || opPackageName == null || bundle == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
return;
}
final boolean isFromConfirmDeviceCredential =
bundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
if (isFromConfirmDeviceCredential) {
checkInternalPermission();
}
// Check the usage of this in system server. Need to remove this check if it becomes
// a public API.
final boolean useDefaultTitle =
bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
if (useDefaultTitle) {
checkInternalPermission();
// Set the default title if necessary
if (TextUtils.isEmpty(bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
bundle.putCharSequence(BiometricPrompt.KEY_TITLE,
getContext().getString(R.string.biometric_dialog_default_title));
}
}
// Launch CDC instead if necessary. CDC will return results through an AIDL call, since
// we can't get activity results. Store the receiver somewhere so we can forward the
// result back to the client.
// TODO(b/123378871): Remove when moved.
if (bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL)) {
mHandler.post(() -> {
final KeyguardManager kgm = getContext().getSystemService(
KeyguardManager.class);
if (!kgm.isDeviceSecure()) {
try {
receiver.onError(
BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
getContext().getString(
R.string.biometric_error_device_not_secured));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
return;
}
mConfirmDeviceCredentialReceiver = receiver;
// Use this so we don't need to duplicate logic..
final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */,
null /* description */, userId);
// Then give it the bundle to do magic behavior..
intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle);
// Create a new task with this activity located at the root.
intent.setFlags(
Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
getContext().startActivityAsUser(intent, UserHandle.CURRENT);
});
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = token;
args.arg2 = sessionId;
args.argi1 = userId;
args.arg3 = receiver;
args.arg4 = opPackageName;
args.arg5 = bundle;
args.argi2 = callingUid;
args.argi3 = callingPid;
args.argi4 = callingUserId;
args.arg6 = callback;
mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
}
@Override // Binder call
public void onConfirmDeviceCredentialSuccess() {
checkInternalPermission();
mHandler.sendEmptyMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS);
}
@Override // Binder call
public void onConfirmDeviceCredentialError(int error, String message) {
checkInternalPermission();
SomeArgs args = SomeArgs.obtain();
args.argi1 = error;
args.arg1 = message;
mHandler.obtainMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR, args).sendToTarget();
}
@Override // Binder call
public void registerCancellationCallback(
IBiometricConfirmDeviceCredentialCallback callback) {
// TODO(b/123378871): Remove when moved.
// This callback replaces the one stored in the current session. If the session is null
// we can ignore this, since it means ConfirmDeviceCredential was launched by something
// else (not BiometricPrompt)
checkInternalPermission();
mHandler.obtainMessage(MSG_REGISTER_CANCELLATION_CALLBACK, callback).sendToTarget();
}
@Override // Binder call
public void cancelAuthentication(IBinder token, String opPackageName)
throws RemoteException {
checkPermission();
SomeArgs args = SomeArgs.obtain();
args.arg1 = token;
args.arg2 = opPackageName;
mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
}
@Override // Binder call
public int canAuthenticate(String opPackageName) {
checkPermission();
final int userId = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
int error;
try {
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
error = result.second;
} finally {
Binder.restoreCallingIdentity(ident);
}
return error;
}
@Override // Binder call
public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
throws RemoteException {
checkInternalPermission();
mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
try {
callback.onChanged(BiometricSourceType.FACE,
mSettingObserver.getFaceEnabledOnKeyguard());
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception", e);
}
}
@Override // Binder call
public void setActiveUser(int userId) {
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
for (int i = 0; i < mAuthenticators.size(); i++) {
mAuthenticators.get(i).getAuthenticator().setActiveUser(userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void resetLockout(byte[] token) {
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
if (mFingerprintService != null) {
mFingerprintService.resetTimeout(token);
}
if (mFaceService != null) {
mFaceService.resetLockout(token);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
private void checkAppOp(String opPackageName, int callingUid) {
if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, callingUid,
opPackageName) != AppOpsManager.MODE_ALLOWED) {
Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied");
throw new SecurityException("Permission denied");
}
}
private void checkInternalPermission() {
getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
"Must have USE_BIOMETRIC_INTERNAL permission");
}
private void checkPermission() {
if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT)
!= PackageManager.PERMISSION_GRANTED) {
getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC,
"Must have USE_BIOMETRIC permission");
}
}
/**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
* and passes it to super.
* </p>
*
* @param context The system server context.
*/
public BiometricService(Context context) {
super(context);
mAppOps = context.getSystemService(AppOpsManager.class);
mEnabledOnKeyguardCallbacks = new ArrayList<>();
mSettingObserver = new SettingObserver(mHandler);
final PackageManager pm = context.getPackageManager();
mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
try {
ActivityManager.getService().registerUserSwitchObserver(
new UserSwitchObserver() {
@Override
public void onUserSwitchComplete(int newUserId) {
mSettingObserver.updateContentObserver();
mSettingObserver.notifyEnabledOnKeyguardCallbacks(newUserId);
}
}, BiometricService.class.getName()
);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register user switch observer", e);
}
}
@Override
public void onStart() {
// TODO: maybe get these on-demand
if (mHasFeatureFingerprint) {
mFingerprintService = IFingerprintService.Stub.asInterface(
ServiceManager.getService(Context.FINGERPRINT_SERVICE));
}
if (mHasFeatureFace) {
mFaceService = IFaceService.Stub.asInterface(
ServiceManager.getService(Context.FACE_SERVICE));
}
mActivityTaskManager = ActivityTaskManager.getService();
mStatusBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
// Cache the authenticators
for (int i = 0; i < FEATURE_ID.length; i++) {
if (hasFeature(FEATURE_ID[i])) {
Authenticator authenticator =
new Authenticator(FEATURE_ID[i], getAuthenticator(FEATURE_ID[i]));
mAuthenticators.add(authenticator);
}
}
publishBinderService(Context.BIOMETRIC_SERVICE, new BiometricServiceWrapper());
}
/**
* Checks if there are any available biometrics, and returns the modality. This method also
* returns errors through the callback (no biometric feature, hardware not detected, no
* templates enrolled, etc). This service must not start authentication if errors are sent.
*
* @Returns A pair [Modality, Error] with Modality being one of
* {@link BiometricAuthenticator#TYPE_NONE},
* {@link BiometricAuthenticator#TYPE_FINGERPRINT},
* {@link BiometricAuthenticator#TYPE_IRIS},
* {@link BiometricAuthenticator#TYPE_FACE}
* and the error containing one of the {@link BiometricConstants} errors.
*/
private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) {
int modality = TYPE_NONE;
// No biometric features, send error
if (mAuthenticators.isEmpty()) {
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
}
// Assuming that authenticators are listed in priority-order, the rest of this function
// will go through and find the first authenticator that's available, enrolled, and enabled.
// The tricky part is returning the correct error. Error strings that are modality-specific
// should also respect the priority-order.
// Find first authenticator that's detected, enrolled, and enabled.
boolean isHardwareDetected = false;
boolean hasTemplatesEnrolled = false;
boolean enabledForApps = false;
int firstHwAvailable = TYPE_NONE;
for (int i = 0; i < mAuthenticators.size(); i++) {
modality = mAuthenticators.get(i).getType();
BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator();
if (authenticator.isHardwareDetected()) {
isHardwareDetected = true;
if (firstHwAvailable == TYPE_NONE) {
// Store the first one since we want to return the error in correct priority
// order.
firstHwAvailable = modality;
}
if (authenticator.hasEnrolledTemplates(userId)) {
hasTemplatesEnrolled = true;
if (isEnabledForApp(modality, userId)) {
// TODO(b/110907543): When face settings (and other settings) have both a
// user toggle as well as a work profile settings page, this needs to be
// updated to reflect the correct setting.
enabledForApps = true;
break;
}
}
}
}
// Check error conditions
if (!isHardwareDetected) {
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
} else if (!hasTemplatesEnrolled) {
// Return the modality here so the correct error string can be sent. This error is
// preferred over !enabledForApps
return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
} else if (!enabledForApps) {
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
}
return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
}
private boolean isEnabledForApp(int modality, int userId) {
switch(modality) {
case TYPE_FINGERPRINT:
return true;
case TYPE_IRIS:
return true;
case TYPE_FACE:
return mSettingObserver.getFaceEnabledForApps(userId);
default:
Slog.w(TAG, "Unsupported modality: " + modality);
return false;
}
}
private String getErrorString(int type, int error, int vendorCode) {
switch (type) {
case TYPE_FINGERPRINT:
return FingerprintManager.getErrorString(getContext(), error, vendorCode);
case TYPE_IRIS:
Slog.w(TAG, "Modality not supported");
return null; // not supported
case TYPE_FACE:
return FaceManager.getErrorString(getContext(), error, vendorCode);
default:
Slog.w(TAG, "Unable to get error string for modality: " + type);
return null;
}
}
private BiometricAuthenticator getAuthenticator(int type) {
switch (type) {
case TYPE_FINGERPRINT:
return (FingerprintManager)
getContext().getSystemService(Context.FINGERPRINT_SERVICE);
case TYPE_IRIS:
return null;
case TYPE_FACE:
return (FaceManager)
getContext().getSystemService(Context.FACE_SERVICE);
default:
return null;
}
}
private boolean hasFeature(int type) {
switch (type) {
case TYPE_FINGERPRINT:
return mHasFeatureFingerprint;
case TYPE_IRIS:
return mHasFeatureIris;
case TYPE_FACE:
return mHasFeatureFace;
default:
return false;
}
}
private void logDialogDismissed(int reason) {
if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
// Explicit auth, authentication confirmed.
// Latency in this case is authenticated -> confirmed. <Biometric>Service
// should have the first half (first acquired -> authenticated).
final long latency = System.currentTimeMillis()
- mCurrentAuthSession.mAuthenticatedTimeMs;
if (LoggableMonitor.DEBUG) {
Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality()
+ ", User: " + mCurrentAuthSession.mUserId
+ ", IsCrypto: " + mCurrentAuthSession.isCrypto()
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", RequireConfirmation: "
+ mCurrentAuthSession.mRequireConfirmation
+ ", State: " + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
+ ", Latency: " + latency);
}
StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED,
statsModality(),
mCurrentAuthSession.mUserId,
mCurrentAuthSession.isCrypto(),
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
mCurrentAuthSession.mRequireConfirmation,
StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
latency,
Utils.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId));
} else {
int error = reason == BiometricPrompt.DISMISSED_REASON_NEGATIVE
? BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON
: reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL
? BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED
: 0;
if (LoggableMonitor.DEBUG) {
Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality()
+ ", User: " + mCurrentAuthSession.mUserId
+ ", IsCrypto: " + mCurrentAuthSession.isCrypto()
+ ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", Error: " + error);
}
// Auth canceled
StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED,
statsModality(),
mCurrentAuthSession.mUserId,
mCurrentAuthSession.isCrypto(),
BiometricsProtoEnums.ACTION_AUTHENTICATE,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
error,
0 /* vendorCode */,
Utils.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId));
}
}
private int statsModality() {
int modality = 0;
if (mCurrentAuthSession == null) {
return BiometricsProtoEnums.MODALITY_UNKNOWN;
}
if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT)
!= 0) {
modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT;
}
if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) {
modality |= BiometricsProtoEnums.MODALITY_IRIS;
}
if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) {
modality |= BiometricsProtoEnums.MODALITY_FACE;
}
return modality;
}
private void handleTaskStackChanged() {
try {
final List<ActivityManager.RunningTaskInfo> runningTasks =
mActivityTaskManager.getTasks(1);
if (!runningTasks.isEmpty()) {
final String topPackage = runningTasks.get(0).topActivity.getPackageName();
if (mCurrentAuthSession != null
&& !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)) {
mStatusBarService.hideBiometricDialog();
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mCurrentAuthSession.mClientReceiver.onError(
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
getContext().getString(
com.android.internal.R.string.biometric_error_canceled)
);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to get running tasks", e);
}
}
private void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] token) {
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
if (mCurrentAuthSession == null) {
Slog.e(TAG, "onAuthenticationSucceeded(): Auth session is null");
return;
}
if (!requireConfirmation) {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
KeyStore.getInstance().addAuthToken(token);
mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
} else {
mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis();
// Store the auth token and submit it to keystore after the confirmation
// button has been pressed.
mCurrentAuthSession.mTokenEscrow = token;
mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM;
}
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
private void handleAuthenticationFailed(String failureReason) {
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
if (mCurrentAuthSession == null) {
Slog.e(TAG, "onAuthenticationFailed(): Auth session is null");
return;
}
mStatusBarService.onBiometricAuthenticated(false, failureReason);
// TODO: This logic will need to be updated if BP is multi-modal
if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
// Pause authentication. onBiometricAuthenticated(false) causes the
// dialog to show a "try again" button for passive modalities.
mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
}
mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
private void handleOnConfirmDeviceCredentialSuccess() {
if (mConfirmDeviceCredentialReceiver == null) {
Slog.w(TAG, "onCDCASuccess null!");
return;
}
try {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
if (mCurrentAuthSession != null) {
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
mConfirmDeviceCredentialReceiver = null;
}
private void handleOnConfirmDeviceCredentialError(int error, String message) {
if (mConfirmDeviceCredentialReceiver == null) {
Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
return;
}
try {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mConfirmDeviceCredentialReceiver.onError(error, message);
if (mCurrentAuthSession != null) {
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
}
mConfirmDeviceCredentialReceiver = null;
}
private void handleRegisterCancellationCallback(
IBiometricConfirmDeviceCredentialCallback callback) {
if (mCurrentAuthSession == null) {
Slog.d(TAG, "Current auth session null");
return;
}
Slog.d(TAG, "Updating cancel callback");
mCurrentAuthSession.mConfirmDeviceCredentialCallback = callback;
}
private void handleOnError(int cookie, int error, String message) {
Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
// Errors can either be from the current auth session or the pending auth session.
// The pending auth session may receive errors such as ERROR_LOCKOUT before
// it becomes the current auth session. Similarly, the current auth session may
// receive errors such as ERROR_CANCELED while the pending auth session is preparing
// to be started. Thus we must match error messages with their cookies to be sure
// of their intended receivers.
try {
if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
if (mCurrentAuthSession.isFromConfirmDeviceCredential()) {
// If we were invoked by ConfirmDeviceCredential, do not delete the current
// auth session since we still need to respond to cancel signal while
if (DEBUG) Slog.d(TAG, "From CDC, transition to CANCELED_SHOWING_CDC state");
// Send the error to ConfirmDeviceCredential so that it goes to Pin/Pattern/Pass
// screen
mCurrentAuthSession.mClientReceiver.onError(error, message);
mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC;
mStatusBarService.hideBiometricDialog();
} else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
mStatusBarService.onBiometricError(message);
if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
mActivityTaskManager.unregisterTaskStackListener(
mTaskStackListener);
mCurrentAuthSession.mClientReceiver.onError(error, message);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} else {
// Send errors after the dialog is dismissed.
mHandler.postDelayed(() -> {
try {
if (mCurrentAuthSession != null) {
mActivityTaskManager.unregisterTaskStackListener(
mTaskStackListener);
mCurrentAuthSession.mClientReceiver.onError(error,
message);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}, BiometricPrompt.HIDE_DIALOG_DELAY);
}
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
// In the "try again" state, we should forward canceled errors to
// the client and and clean up.
mCurrentAuthSession.mClientReceiver.onError(error, message);
mStatusBarService.onBiometricError(message);
mActivityTaskManager.unregisterTaskStackListener(
mTaskStackListener);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
} else {
Slog.e(TAG, "Impossible session error state: "
+ mCurrentAuthSession.mState);
}
} else if (mPendingAuthSession != null
&& mPendingAuthSession.containsCookie(cookie)) {
if (mPendingAuthSession.mState == STATE_AUTH_CALLED) {
mPendingAuthSession.mClientReceiver.onError(error, message);
mPendingAuthSession.mState = STATE_AUTH_IDLE;
mPendingAuthSession = null;
} else {
Slog.e(TAG, "Impossible pending session error state: "
+ mPendingAuthSession.mState);
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
private void handleOnAcquired(int acquiredInfo, String message) {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
if (mCurrentAuthSession == null) {
Slog.e(TAG, "onAcquired(): Auth session is null");
return;
}
if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
if (message == null) {
Slog.w(TAG, "Ignoring null message: " + acquiredInfo);
return;
}
try {
mStatusBarService.onBiometricHelp(message);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
}
private void handleOnDismissed(int reason) {
if (mCurrentAuthSession == null) {
Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null");
return;
}
logDialogDismissed(reason);
try {
if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
// Positive button is used by passive modalities as a "confirm" button,
// do not send to client
mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelInternal(null /* token */, null /* package */, false /* fromClient */);
}
if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
mCurrentAuthSession.mClientReceiver.onError(
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
getContext().getString(
com.android.internal.R.string.biometric_error_user_canceled));
} else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
// Have the service send the token to KeyStore, and send onAuthenticated
// to the application
KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow);
mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
}
// Do not clean up yet if we are from ConfirmDeviceCredential. We should be in the
// STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC. The session should only be removed when
// ConfirmDeviceCredential is confirmed or canceled.
// TODO(b/123378871): Remove when moved
if (!mCurrentAuthSession.isFromConfirmDeviceCredential()) {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
private void handleOnTryAgainPressed() {
Slog.d(TAG, "onTryAgainPressed");
// No need to check permission, since it can only be invoked by SystemUI
// (or system server itself).
authenticateInternal(mCurrentAuthSession.mToken,
mCurrentAuthSession.mSessionId,
mCurrentAuthSession.mUserId,
mCurrentAuthSession.mClientReceiver,
mCurrentAuthSession.mOpPackageName,
mCurrentAuthSession.mBundle,
mCurrentAuthSession.mCallingUid,
mCurrentAuthSession.mCallingPid,
mCurrentAuthSession.mCallingUserId,
mCurrentAuthSession.mModality,
mCurrentAuthSession.mConfirmDeviceCredentialCallback);
}
private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation,
int userId) {
Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
if (pair.getValue() == cookie) {
mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue());
mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey());
Slog.d(TAG, "Matched cookie: " + cookie + ", "
+ mPendingAuthSession.mModalitiesWaiting.size() + " remaining");
break;
}
}
if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
final boolean continuing = mCurrentAuthSession != null
&& mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
mCurrentAuthSession = mPendingAuthSession;
mPendingAuthSession = null;
mCurrentAuthSession.mState = STATE_AUTH_STARTED;
try {
int modality = TYPE_NONE;
it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
if (pair.getKey() == TYPE_FINGERPRINT) {
mFingerprintService.startPreparedClient(pair.getValue());
} else if (pair.getKey() == TYPE_IRIS) {
Slog.e(TAG, "Iris unsupported");
} else if (pair.getKey() == TYPE_FACE) {
mFaceService.startPreparedClient(pair.getValue());
} else {
Slog.e(TAG, "Unknown modality: " + pair.getKey());
}
modality |= pair.getKey();
}
if (!continuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId);
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
}
private void handleAuthenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
int callingUid, int callingPid, int callingUserId,
IBiometricConfirmDeviceCredentialCallback callback) {
mHandler.post(() -> {
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
final int modality = result.first;
final int error = result.second;
// Check for errors, notify callback, and return
if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
try {
final String hardwareUnavailable =
getContext().getString(R.string.biometric_error_hw_unavailable);
switch (error) {
case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
receiver.onError(error,
getErrorString(modality, error, 0 /* vendorCode */));
break;
default:
Slog.e(TAG, "Unhandled error");
break;
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
return;
}
mCurrentModality = modality;
// Start preparing for authentication. Authentication starts when
// all modalities requested have invoked onReadyForAuthentication.
authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
callingUid, callingPid, callingUserId, modality, callback);
});
}
/**
* authenticate() (above) which is called from BiometricPrompt determines which
* modality/modalities to start authenticating with. authenticateInternal() should only be
* used for:
* 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
* invoked, shortly after which BiometricPrompt is shown and authentication starts
* 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
* and the user has pressed "try again"
*/
private void authenticateInternal(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
int callingUid, int callingPid, int callingUserId, int modality,
IBiometricConfirmDeviceCredentialCallback callback) {
try {
boolean requireConfirmation = bundle.getBoolean(
BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
if ((modality & TYPE_FACE) != 0) {
// Check if the user has forced confirmation to be required in Settings.
requireConfirmation = requireConfirmation
|| mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
}
// Generate random cookies to pass to the services that should prepare to start
// authenticating. Store the cookie here and wait for all services to "ack"
// 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;
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,
receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
modality, requireConfirmation, callback);
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);
}
}
private void handleCancelAuthentication(IBinder token, String opPackageName) {
if (token == null || opPackageName == null) {
Slog.e(TAG, "Unable to cancel, one or more null arguments");
return;
}
if (mCurrentAuthSession != null
&& mCurrentAuthSession.mState == STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC) {
if (DEBUG) Slog.d(TAG, "Cancel received while ConfirmDeviceCredential showing");
try {
mCurrentAuthSession.mConfirmDeviceCredentialCallback.cancel();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel ConfirmDeviceCredential", e);
}
// TODO(b/123378871): Remove when moved. Piggy back on this for now to clean up.
handleOnConfirmDeviceCredentialError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
getContext().getString(R.string.biometric_error_canceled));
} else if (mCurrentAuthSession != null
&& mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
// We need to check the current authenticators state. If we're pending confirm
// or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
// since we won't be getting an onError from the driver.
try {
// Send error to client
mCurrentAuthSession.mClientReceiver.onError(
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
getContext().getString(
com.android.internal.R.string.biometric_error_user_canceled)
);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
} else {
boolean fromCDC = false;
if (mCurrentAuthSession != null) {
fromCDC = mCurrentAuthSession.mBundle.getBoolean(
BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
}
if (fromCDC) {
if (DEBUG) Slog.d(TAG, "Cancelling from CDC");
cancelInternal(token, opPackageName, false /* fromClient */);
} else {
cancelInternal(token, opPackageName, true /* fromClient */);
}
}
}
void cancelInternal(IBinder token, String opPackageName, boolean fromClient) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
mHandler.post(() -> {
try {
// TODO: For multiple modalities, send a single ERROR_CANCELED only when all
// drivers have canceled authentication.
if ((mCurrentModality & TYPE_FINGERPRINT) != 0) {
mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId, fromClient);
}
if ((mCurrentModality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((mCurrentModality & TYPE_FACE) != 0) {
mFaceService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId, fromClient);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
}
});
}
}