blob: b65535af52b1a03316a24ffaeec2edcf66fe99da [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.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.SynchronousUserSwitchObserver;
import android.app.TaskStackListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.fingerprint.Fingerprint;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.IHwBinder;
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.StatsLog;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
import com.android.server.biometrics.fingerprint.FingerprintService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Abstract base class containing all of the business logic for biometric services, e.g.
* Fingerprint, Face, Iris.
*
* @hide
*/
public abstract class BiometricServiceBase extends SystemService
implements IHwBinder.DeathRecipient {
protected static final boolean DEBUG = true;
private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
private static final int MSG_USER_SWITCHING = 10;
private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30 * 1000;
private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
private final Context mContext;
private final String mKeyguardPackage;
private final SparseBooleanArray mTimedLockoutCleared;
private final SparseIntArray mFailedAttempts;
private final IActivityTaskManager mActivityTaskManager;
private final AlarmManager mAlarmManager;
private final PowerManager mPowerManager;
private final UserManager mUserManager;
private final MetricsLogger mMetricsLogger;
private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener();
private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable();
private final LockoutReceiver mLockoutReceiver = new LockoutReceiver();
private final ArrayList<LockoutResetMonitor> mLockoutMonitors = new ArrayList<>();
protected final IStatusBarService mStatusBarService;
protected final Map<Integer, Long> mAuthenticatorIds =
Collections.synchronizedMap(new HashMap<>());
protected final ResetFailedAttemptsForUserRunnable mResetFailedAttemptsForCurrentUserRunnable =
new ResetFailedAttemptsForUserRunnable();
protected final AppOpsManager mAppOps;
protected final H mHandler = new H();
private IBiometricService mBiometricService;
private ClientMonitor mCurrentClient;
private ClientMonitor mPendingClient;
private PerformanceStats mPerformanceStats;
protected int mCurrentUserId = UserHandle.USER_NULL;
// Tracks if the current authentication makes use of CryptoObjects.
protected boolean mIsCrypto;
// Normal authentications are tracked by mPerformanceMap.
protected HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
// Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap.
protected HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>();
protected int mHALDeathCount;
protected class PerformanceStats {
public int accept; // number of accepted biometrics
public int reject; // number of rejected biometrics
public int acquire; // total number of acquisitions. Should be >= accept+reject due to poor
// image acquisition in some cases (too fast, too slow, dirty sensor, etc.)
public int lockout; // total number of lockouts
public int permanentLockout; // total number of permanent lockouts
}
/**
* @return the log tag.
*/
protected abstract String getTag();
/**
* @return the biometric utilities for a specific implementation.
*/
protected abstract BiometricUtils getBiometricUtils();
/**
* @return the number of failed attempts after which the user will be temporarily locked out
* from using the biometric. A strong auth (pin/pattern/pass) clears this counter.
*/
protected abstract int getFailedAttemptsLockoutTimed();
/**
* @return the number of failed attempts after which the user will be permanently locked out
* from using the biometric. A strong auth (pin/pattern/pass) clears this counter.
*/
protected abstract int getFailedAttemptsLockoutPermanent();
/**
* @return the metrics constants for a biometric implementation.
*/
protected abstract Metrics getMetrics();
/**
* @param userId
* @return true if the enrollment limit has been reached.
*/
protected abstract boolean hasReachedEnrollmentLimit(int userId);
/**
* Notifies the HAL that the user has changed.
* @param userId
* @param clientPackage
*/
protected abstract void updateActiveGroup(int userId, String clientPackage);
/**
* @return The protected intent to reset lockout for a specific biometric.
*/
protected abstract String getLockoutResetIntent();
/**
* @return The permission the sender is required to have in order for the lockout reset intent
* to be received by the BiometricService implementation.
*/
protected abstract String getLockoutBroadcastPermission();
/**
* @return The HAL ID.
*/
protected abstract long getHalDeviceId();
/**
* This method is called when the user switches. Implementations should probably notify the
* HAL.
* @param userId
*/
protected abstract void handleUserSwitching(int userId);
/**
* @param userId
* @return Returns true if the user has any enrolled biometrics.
*/
protected abstract boolean hasEnrolledBiometrics(int userId);
/**
* @return Returns the MANAGE_* permission string, which is required for enrollment, removal
* etc.
*/
protected abstract String getManageBiometricPermission();
/**
* Checks if the caller has permission to use the biometric service - throws a SecurityException
* if not.
*/
protected abstract void checkUseBiometricPermission();
/**
* Checks if the caller passes the app ops check
*/
protected abstract boolean checkAppOps(int uid, String opPackageName);
/**
* Notifies clients of any change in the biometric state (active / idle). This is mainly for
* Fingerprint navigation gestures.
* @param isActive
*/
protected void notifyClientActiveCallbacks(boolean isActive) {}
protected abstract int statsModality();
protected abstract class AuthenticationClientImpl extends AuthenticationClient {
// Used to check if the public API that was invoked was from FingerprintManager. Only
// to be overridden by FingerprintService.
protected boolean isFingerprint() {
return false;
}
public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
boolean restricted, String owner, int cookie, boolean requireConfirmation) {
super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId,
groupId, opId, restricted, owner, cookie, requireConfirmation);
}
@Override
protected int statsClient() {
if (isKeyguard(getOwnerString())) {
return BiometricsProtoEnums.CLIENT_KEYGUARD;
} else if (isBiometricPrompt()) {
return BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT;
} else if (isFingerprint()) {
return BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;
} else {
return BiometricsProtoEnums.CLIENT_UNKNOWN;
}
}
@Override
public void onStart() {
try {
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
} catch (RemoteException e) {
Slog.e(getTag(), "Could not register task stack listener", e);
}
}
@Override
public void onStop() {
try {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
} catch (RemoteException e) {
Slog.e(getTag(), "Could not unregister task stack listener", e);
}
}
@Override
public void resetFailedAttempts() {
resetFailedAttemptsForUser(true /* clearAttemptCounter */,
ActivityManager.getCurrentUser());
}
@Override
public void notifyUserActivity() {
userActivity();
}
@Override
public int handleFailedAttempt() {
final int currentUser = ActivityManager.getCurrentUser();
mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);
final int lockoutMode = getLockoutMode();
if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
mPerformanceStats.permanentLockout++;
} else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
mPerformanceStats.lockout++;
}
// Failing multiple times will continue to push out the lockout time
if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
scheduleLockoutResetForUser(currentUser);
return lockoutMode;
}
return AuthenticationClient.LOCKOUT_NONE;
}
}
protected abstract class EnrollClientImpl extends EnrollClient {
public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
IBinder token, ServiceListener listener, int userId, int groupId,
byte[] cryptoToken, boolean restricted, String owner,
final int[] disabledFeatures) {
super(context, getMetrics(), daemon, halDeviceId, token, listener,
userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(),
disabledFeatures);
}
@Override
public void notifyUserActivity() {
userActivity();
}
}
protected abstract class RemovalClientImpl extends RemovalClient {
private boolean mShouldNotify;
public RemovalClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
IBinder token, ServiceListener listener, int fingerId, int groupId, int userId,
boolean restricted, String owner) {
super(context, getMetrics(), daemon, halDeviceId, token, listener, fingerId, groupId,
userId, restricted, owner, getBiometricUtils());
}
public void setShouldNotifyUserActivity(boolean shouldNotify) {
mShouldNotify = shouldNotify;
}
@Override
public void notifyUserActivity() {
if (mShouldNotify) {
userActivity();
}
}
}
protected abstract class EnumerateClientImpl extends EnumerateClient {
public EnumerateClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
IBinder token, ServiceListener listener, int groupId, int userId,
boolean restricted, String owner) {
super(context, getMetrics(), daemon, halDeviceId, token, listener, groupId, userId,
restricted, owner);
}
@Override
public void notifyUserActivity() {
userActivity();
}
}
/**
* Wraps the callback interface from Service -> Manager
*/
protected interface ServiceListener {
default void onEnrollResult(BiometricAuthenticator.Identifier identifier,
int remaining) throws RemoteException {};
void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException;
default void onAuthenticationSucceeded(long deviceId,
BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
default void onAuthenticationFailed(long deviceId) throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException;
default void onRemoved(BiometricAuthenticator.Identifier identifier,
int remaining) throws RemoteException {};
default void onEnumerated(BiometricAuthenticator.Identifier identifier,
int remaining) throws RemoteException {};
}
/**
* Wraps the callback interface from Service -> BiometricPrompt
*/
protected abstract class BiometricServiceListener implements ServiceListener {
private IBiometricServiceReceiverInternal mWrapperReceiver;
public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) {
mWrapperReceiver = wrapperReceiver;
}
public IBiometricServiceReceiverInternal getWrapperReceiver() {
return mWrapperReceiver;
}
@Override
public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token);
}
}
@Override
public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
}
}
}
/**
* Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor
* subclasses.
*/
protected interface DaemonWrapper {
int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. see errno.h.
int authenticate(long operationId, int groupId) throws RemoteException;
int cancel() throws RemoteException;
int remove(int groupId, int biometricId) throws RemoteException;
int enumerate() throws RemoteException;
int enroll(byte[] cryptoToken, int groupId, int timeout,
ArrayList<Integer> disabledFeatures) throws RemoteException;
}
/**
* Handler which all subclasses should post events to.
*/
protected final class H extends Handler {
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_USER_SWITCHING:
handleUserSwitching(msg.arg1);
break;
default:
Slog.w(getTag(), "Unknown message:" + msg.what);
}
}
}
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
try {
if (!(mCurrentClient instanceof AuthenticationClient)) {
return;
}
final String currentClient = mCurrentClient.getOwnerString();
if (isKeyguard(currentClient)) {
return; // Keyguard is always allowed
}
List<ActivityManager.RunningTaskInfo> runningTasks =
mActivityTaskManager.getTasks(1);
if (!runningTasks.isEmpty()) {
final String topPackage = runningTasks.get(0).topActivity.getPackageName();
if (!topPackage.contentEquals(currentClient)
&& !mCurrentClient.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication, top: " + topPackage
+ " currentClient: " + currentClient);
mCurrentClient.stop(false /* initiatedByClient */);
}
}
} catch (RemoteException e) {
Slog.e(getTag(), "Unable to get running tasks", e);
}
}
}
private final class ResetClientStateRunnable implements Runnable {
@Override
public void run() {
/**
* Warning: if we get here, the driver never confirmed our call to cancel the current
* operation (authenticate, enroll, remove, enumerate, etc), which is
* really bad. The result will be a 3-second delay in starting each new client.
* If you see this on a device, make certain the driver notifies with
* {@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} in response to cancel()
* once it has successfully switched to the IDLE state in the HAL.
* Additionally,{@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} should only be sent
* in response to an actual cancel() call.
*/
Slog.w(getTag(), "Client "
+ (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
+ " failed to respond to cancel, starting client "
+ (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
mCurrentClient = null;
startClient(mPendingClient, false);
}
}
private final class LockoutReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Slog.v(getTag(), "Resetting lockout: " + intent.getAction());
if (getLockoutResetIntent().equals(intent.getAction())) {
final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
}
}
}
private final class ResetFailedAttemptsForUserRunnable implements Runnable {
@Override
public void run() {
resetFailedAttemptsForUser(true /* clearAttemptCounter */,
ActivityManager.getCurrentUser());
}
}
private final class LockoutResetMonitor implements IBinder.DeathRecipient {
private static final long WAKELOCK_TIMEOUT_MS = 2000;
private final IBiometricServiceLockoutResetCallback mCallback;
private final PowerManager.WakeLock mWakeLock;
public LockoutResetMonitor(IBiometricServiceLockoutResetCallback callback) {
mCallback = callback;
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"lockout reset callback");
try {
mCallback.asBinder().linkToDeath(LockoutResetMonitor.this, 0);
} catch (RemoteException e) {
Slog.w(getTag(), "caught remote exception in linkToDeath", e);
}
}
public void sendLockoutReset() {
if (mCallback != null) {
try {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
mCallback.onLockoutReset(getHalDeviceId(), new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
releaseWakelock();
}
});
} catch (DeadObjectException e) {
Slog.w(getTag(), "Death object while invoking onLockoutReset: ", e);
mHandler.post(mRemoveCallbackRunnable);
} catch (RemoteException e) {
Slog.w(getTag(), "Failed to invoke onLockoutReset: ", e);
releaseWakelock();
}
}
}
private final Runnable mRemoveCallbackRunnable = new Runnable() {
@Override
public void run() {
releaseWakelock();
removeLockoutResetCallback(LockoutResetMonitor.this);
}
};
@Override
public void binderDied() {
Slog.e(getTag(), "Lockout reset callback binder died");
mHandler.post(mRemoveCallbackRunnable);
}
private void releaseWakelock() {
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
}
}
/**
* 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 BiometricServiceBase(Context context) {
super(context);
mContext = context;
mStatusBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString(
com.android.internal.R.string.config_keyguardComponent)).getPackageName();
mAppOps = context.getSystemService(AppOpsManager.class);
mTimedLockoutCleared = new SparseBooleanArray();
mFailedAttempts = new SparseIntArray();
mActivityTaskManager = ((ActivityTaskManager) context.getSystemService(
Context.ACTIVITY_TASK_SERVICE)).getService();
mPowerManager = mContext.getSystemService(PowerManager.class);
mAlarmManager = mContext.getSystemService(AlarmManager.class);
mUserManager = UserManager.get(mContext);
mMetricsLogger = new MetricsLogger();
mContext.registerReceiver(mLockoutReceiver, new IntentFilter(getLockoutResetIntent()),
getLockoutBroadcastPermission(), null /* handler */);
}
@Override
public void onStart() {
listenForUserSwitches();
}
@Override
public void serviceDied(long cookie) {
Slog.e(getTag(), "HAL died");
mMetricsLogger.count(getMetrics().tagHalDied(), 1);
mHALDeathCount++;
handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
0 /*vendorCode */);
StatsLog.write(StatsLog.BIOMETRIC_HAL_DEATH_REPORTED, statsModality());
}
protected ClientMonitor getCurrentClient() {
return mCurrentClient;
}
protected ClientMonitor getPendingClient() {
return mPendingClient;
}
/**
* Callback handlers from the daemon. The caller must put this on a handler.
*/
protected void handleAcquired(long deviceId, int acquiredInfo, int vendorCode) {
ClientMonitor client = mCurrentClient;
if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
removeClient(client);
}
if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
&& client instanceof AuthenticationClient) {
// ignore enrollment acquisitions or acquisitions when we're locked out
mPerformanceStats.acquire++;
}
}
protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier,
ArrayList<Byte> token) {
ClientMonitor client = mCurrentClient;
final boolean authenticated = identifier.getBiometricId() != 0;
if (client != null && client.onAuthenticated(identifier, authenticated, token)) {
removeClient(client);
}
if (authenticated) {
mPerformanceStats.accept++;
} else {
mPerformanceStats.reject++;
}
}
protected void handleEnrollResult(BiometricAuthenticator.Identifier identifier,
int remaining) {
ClientMonitor client = mCurrentClient;
if (client != null && client.onEnrollResult(identifier, remaining)) {
removeClient(client);
// When enrollment finishes, update this group's authenticator id, as the HAL has
// already generated a new authenticator id when the new biometric is enrolled.
if (identifier instanceof Fingerprint) {
updateActiveGroup(((Fingerprint)identifier).getGroupId(), null);
} else {
updateActiveGroup(mCurrentUserId, null);
}
}
}
protected void handleError(long deviceId, int error, int vendorCode) {
final ClientMonitor client = mCurrentClient;
if (DEBUG) Slog.v(getTag(), "handleError(client="
+ (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
if (client != null && client.onError(deviceId, error, vendorCode)) {
removeClient(client);
}
if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
mHandler.removeCallbacks(mResetClientState);
if (mPendingClient != null) {
if (DEBUG) Slog.v(getTag(), "start pending client " +
mPendingClient.getOwnerString());
startClient(mPendingClient, false);
mPendingClient = null;
}
}
}
protected void handleRemoved(BiometricAuthenticator.Identifier identifier,
final int remaining) {
if (DEBUG) Slog.w(getTag(), "Removed: fid=" + identifier.getBiometricId()
+ ", dev=" + identifier.getDeviceId()
+ ", rem=" + remaining);
ClientMonitor client = mCurrentClient;
if (client != null && client.onRemoved(identifier, remaining)) {
removeClient(client);
// When the last biometric of a group is removed, update the authenticator id
int userId = mCurrentUserId;
if (identifier instanceof Fingerprint) {
userId = ((Fingerprint) identifier).getGroupId();
}
if (!hasEnrolledBiometrics(userId)) {
updateActiveGroup(userId, null);
}
}
}
/**
* Calls from the Manager. These are still on the calling binder's thread.
*/
protected void enrollInternal(EnrollClientImpl client, int userId) {
if (hasReachedEnrollmentLimit(userId)) {
return;
}
// Group ID is arbitrarily set to parent profile user ID. It just represents
// the default biometrics for the user.
if (!isCurrentUserOrProfile(userId)) {
return;
}
mHandler.post(() -> {
startClient(client, true /* initiatedByClient */);
});
}
protected void cancelEnrollmentInternal(IBinder token) {
mHandler.post(() -> {
ClientMonitor client = mCurrentClient;
if (client instanceof EnrollClient && client.getToken() == token) {
client.stop(client.getToken() == token);
}
});
}
protected void authenticateInternal(AuthenticationClientImpl client, long opId,
String opPackageName) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId);
}
protected void authenticateInternal(AuthenticationClientImpl client, long opId,
String opPackageName, int callingUid, int callingPid, int callingUserId) {
if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
callingUserId)) {
if (DEBUG) Slog.v(getTag(), "authenticate(): reject " + opPackageName);
return;
}
mHandler.post(() -> {
mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0);
// Get performance stats object for this user.
HashMap<Integer, PerformanceStats> pmap
= (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;
PerformanceStats stats = pmap.get(mCurrentUserId);
if (stats == null) {
stats = new PerformanceStats();
pmap.put(mCurrentUserId, stats);
}
mPerformanceStats = stats;
mIsCrypto = (opId != 0);
startAuthentication(client, opPackageName);
});
}
protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId,
true /* fromClient */);
}
protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId, boolean fromClient) {
if (fromClient) {
// Only check this if cancel was called from the client (app). If cancel was called
// from BiometricService, it means the dialog was dismissed due to user interaction.
if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
callingUserId)) {
if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
return;
}
}
mHandler.post(() -> {
ClientMonitor client = mCurrentClient;
if (client instanceof AuthenticationClient) {
if (client.getToken() == token || !fromClient) {
if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString()
+ ", fromClient: " + fromClient);
// If cancel was from BiometricService, it means the dialog was dismissed
// and authentication should be canceled.
client.stop(client.getToken() == token);
} else {
if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString()
+ " since tokens don't match. fromClient: " + fromClient);
}
} else if (client != null) {
if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client "
+ client.getOwnerString());
}
});
}
protected void setActiveUserInternal(int userId) {
mHandler.post(() -> {
updateActiveGroup(userId, null /* clientPackage */);
});
}
protected void removeInternal(RemovalClientImpl client) {
mHandler.post(() -> {
startClient(client, true /* initiatedByClient */);
});
}
protected void enumerateInternal(EnumerateClientImpl client) {
mHandler.post(() -> {
startClient(client, true /* initiatedByClient */);
});
}
// Should be done on a handler thread - not on the Binder's thread.
private void startAuthentication(AuthenticationClientImpl client, String opPackageName) {
if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");
int lockoutMode = getLockoutMode();
if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
if (!client.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */)) {
Slog.w(getTag(), "Cannot send permanent lockout message to client");
}
return;
}
startClient(client, true /* initiatedByClient */);
}
protected void addLockoutResetCallback(IBiometricServiceLockoutResetCallback callback) {
mHandler.post(() -> {
final LockoutResetMonitor monitor = new LockoutResetMonitor(callback);
if (!mLockoutMonitors.contains(monitor)) {
mLockoutMonitors.add(monitor);
}
});
}
/**
* Helper methods.
*/
/**
* @param opPackageName name of package for caller
* @param requireForeground only allow this call while app is in the foreground
* @return true if caller can use the biometric API
*/
protected boolean canUseBiometric(String opPackageName, boolean requireForeground, int uid,
int pid, int userId) {
checkUseBiometricPermission();
if (isKeyguard(opPackageName)) {
return true; // Keyguard is always allowed
}
if (!isCurrentUserOrProfile(userId)) {
Slog.w(getTag(), "Rejecting " + opPackageName + "; not a current user or profile");
return false;
}
if (!checkAppOps(uid, opPackageName)) {
Slog.w(getTag(), "Rejecting " + opPackageName + "; permission denied");
return false;
}
if (requireForeground && !(isForegroundActivity(uid, pid) || isCurrentClient(
opPackageName))) {
Slog.w(getTag(), "Rejecting " + opPackageName + "; not in foreground");
return false;
}
return true;
}
/**
* @param opPackageName package of the caller
* @return true if this is the same client currently using the biometric
*/
private boolean isCurrentClient(String opPackageName) {
return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName);
}
/**
* @return true if this is keyguard package
*/
private boolean isKeyguard(String clientPackage) {
return mKeyguardPackage.equals(clientPackage);
}
protected int getLockoutMode() {
final int currentUser = ActivityManager.getCurrentUser();
final int failedAttempts = mFailedAttempts.get(currentUser, 0);
if (failedAttempts >= getFailedAttemptsLockoutPermanent()) {
return AuthenticationClient.LOCKOUT_PERMANENT;
} else if (failedAttempts > 0 &&
mTimedLockoutCleared.get(currentUser, false) == false
&& (failedAttempts % getFailedAttemptsLockoutTimed() == 0)) {
return AuthenticationClient.LOCKOUT_TIMED;
}
return AuthenticationClient.LOCKOUT_NONE;
}
private boolean isForegroundActivity(int uid, int pid) {
try {
List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
int N = procs.size();
for (int i = 0; i < N; i++) {
ActivityManager.RunningAppProcessInfo proc = procs.get(i);
if (proc.pid == pid && proc.uid == uid
&& proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
return true;
}
}
} catch (RemoteException e) {
Slog.w(getTag(), "am.getRunningAppProcesses() failed");
}
return false;
}
/**
* Calls the HAL to switch states to the new task. If there's already a current task,
* it calls cancel() and sets mPendingClient to begin when the current task finishes
* ({@link BiometricConstants#BIOMETRIC_ERROR_CANCELED}).
*
* @param newClient the new client that wants to connect
* @param initiatedByClient true for authenticate, remove and enroll
*/
private void startClient(ClientMonitor newClient, boolean initiatedByClient) {
ClientMonitor currentClient = mCurrentClient;
if (currentClient != null) {
if (DEBUG) Slog.v(getTag(), "request stop current client " +
currentClient.getOwnerString());
// This check only matters for FingerprintService, since enumerate may call back
// multiple times.
if (currentClient instanceof FingerprintService.EnumerateClientImpl ||
currentClient instanceof FingerprintService.RemovalClientImpl) {
// This condition means we're currently running internal diagnostics to
// remove extra fingerprints in the hardware and/or the software
// TODO: design an escape hatch in case client never finishes
if (newClient != null) {
Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
+ newClient.getClass().getSuperclass().getSimpleName()
+ "(" + newClient.getOwnerString() + ")"
+ ", initiatedByClient = " + initiatedByClient);
}
} else {
currentClient.stop(initiatedByClient);
}
mPendingClient = newClient;
mHandler.removeCallbacks(mResetClientState);
mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
} else if (newClient != null) {
// For BiometricPrompt clients, do not start until
// <Biometric>Service#startPreparedClient is called. BiometricService waits until all
// modalities are ready before initiating authentication.
if (newClient instanceof AuthenticationClient) {
AuthenticationClient client = (AuthenticationClient) newClient;
if (client.isBiometricPrompt()) {
if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
mCurrentClient = newClient;
if (mBiometricService == null) {
mBiometricService = IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE));
}
try {
mBiometricService.onReadyForAuthentication(client.getCookie(),
client.getRequireConfirmation(), client.getTargetUserId());
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception", e);
}
return;
}
}
// We are not a BiometricPrompt client, start the client immediately
mCurrentClient = newClient;
startCurrentClient(mCurrentClient.getCookie());
}
}
protected void startCurrentClient(int cookie) {
if (mCurrentClient == null) {
Slog.e(getTag(), "Trying to start null client!");
return;
}
if (DEBUG) Slog.v(getTag(), "starting client "
+ mCurrentClient.getClass().getSuperclass().getSimpleName()
+ "(" + mCurrentClient.getOwnerString() + ")"
+ " cookie: " + cookie + "/" + mCurrentClient.getCookie());
if (cookie != mCurrentClient.getCookie()) {
Slog.e(getTag(), "Mismatched cookie");
return;
}
notifyClientActiveCallbacks(true);
mCurrentClient.start();
}
protected void removeClient(ClientMonitor client) {
if (client != null) {
client.destroy();
if (client != mCurrentClient && mCurrentClient != null) {
Slog.w(getTag(), "Unexpected client: " + client.getOwnerString() + "expected: "
+ mCurrentClient.getOwnerString());
}
}
if (mCurrentClient != null) {
if (DEBUG) Slog.v(getTag(), "Done with client: " + client.getOwnerString());
mCurrentClient = null;
}
if (mPendingClient == null) {
notifyClientActiveCallbacks(false);
}
}
/**
* Populates existing authenticator ids. To be used only during the start of the service.
*/
protected void loadAuthenticatorIds() {
// This operation can be expensive, so keep track of the elapsed time. Might need to move to
// background if it takes too long.
long t = System.currentTimeMillis();
mAuthenticatorIds.clear();
for (UserInfo user : UserManager.get(getContext()).getUsers(true /* excludeDying */)) {
int userId = getUserOrWorkProfileId(null, user.id);
if (!mAuthenticatorIds.containsKey(userId)) {
updateActiveGroup(userId, null);
}
}
t = System.currentTimeMillis() - t;
if (t > 1000) {
Slog.w(getTag(), "loadAuthenticatorIds() taking too long: " + t + "ms");
}
}
/**
* @param clientPackage the package of the caller
* @return the profile id
*/
protected int getUserOrWorkProfileId(String clientPackage, int userId) {
if (!isKeyguard(clientPackage) && isWorkProfile(userId)) {
return userId;
}
return getEffectiveUserId(userId);
}
protected boolean isRestricted() {
// Only give privileged apps (like Settings) access to biometric info
final boolean restricted = !hasPermission(getManageBiometricPermission());
return restricted;
}
protected boolean hasPermission(String permission) {
return getContext().checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
}
protected void checkPermission(String permission) {
getContext().enforceCallingOrSelfPermission(permission,
"Must have " + permission + " permission.");
}
protected boolean isCurrentUserOrProfile(int userId) {
UserManager um = UserManager.get(mContext);
if (um == null) {
Slog.e(getTag(), "Unable to acquire UserManager");
return false;
}
final long token = Binder.clearCallingIdentity();
try {
// Allow current user or profiles of the current user...
for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
if (profileId == userId) {
return true;
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
return false;
}
/***
* @param opPackageName the name of the calling package
* @return authenticator id for the calling user
*/
protected long getAuthenticatorId(String opPackageName) {
final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
return mAuthenticatorIds.getOrDefault(userId, 0L);
}
private void scheduleLockoutResetForUser(int userId) {
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
getLockoutResetIntentForUser(userId));
}
private PendingIntent getLockoutResetIntentForUser(int userId) {
return PendingIntent.getBroadcast(mContext, userId,
new Intent(getLockoutResetIntent()).putExtra(KEY_LOCKOUT_RESET_USER, userId),
PendingIntent.FLAG_UPDATE_CURRENT);
}
private void userActivity() {
long now = SystemClock.uptimeMillis();
mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
}
/**
* @param userId
* @return true if this is a work profile
*/
private boolean isWorkProfile(int userId) {
UserInfo userInfo = null;
final long token = Binder.clearCallingIdentity();
try {
userInfo = mUserManager.getUserInfo(userId);
} finally {
Binder.restoreCallingIdentity(token);
}
return userInfo != null && userInfo.isManagedProfile();
}
private int getEffectiveUserId(int userId) {
UserManager um = UserManager.get(mContext);
if (um != null) {
final long callingIdentity = Binder.clearCallingIdentity();
userId = um.getCredentialOwnerProfile(userId);
Binder.restoreCallingIdentity(callingIdentity);
} else {
Slog.e(getTag(), "Unable to acquire UserManager");
}
return userId;
}
// Attempt counter should only be cleared when Keyguard goes away or when
// a biometric is successfully authenticated.
private void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
Slog.v(getTag(), "Reset biometric lockout, clearAttemptCounter=" + clearAttemptCounter);
}
if (clearAttemptCounter) {
mFailedAttempts.put(userId, 0);
}
mTimedLockoutCleared.put(userId, true);
// If we're asked to reset failed attempts externally (i.e. from Keyguard),
// the alarm might still be pending; remove it.
cancelLockoutResetForUser(userId);
notifyLockoutResetMonitors();
}
private void cancelLockoutResetForUser(int userId) {
mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
}
private void listenForUserSwitches() {
try {
ActivityManager.getService().registerUserSwitchObserver(
new SynchronousUserSwitchObserver() {
@Override
public void onUserSwitching(int newUserId) throws RemoteException {
mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
.sendToTarget();
}
}, getTag());
} catch (RemoteException e) {
Slog.w(getTag(), "Failed to listen for user switching event" ,e);
}
}
private void notifyLockoutResetMonitors() {
for (int i = 0; i < mLockoutMonitors.size(); i++) {
mLockoutMonitors.get(i).sendLockoutReset();
}
}
private void removeLockoutResetCallback(
LockoutResetMonitor monitor) {
mLockoutMonitors.remove(monitor);
}
}