-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
* 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.
+ * limitations under the License
*/
-package com.android.server.biometrics.fingerprint;
+package com.android.server.biometrics.common;
import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
/**
private Bundle mBundle;
private IStatusBarService mStatusBarService;
private boolean mInLockout;
+ // TODO: BiometricManager, after other biometric modalities are introduced.
private final FingerprintManager mFingerprintManager;
protected boolean mDialogDismissed;
try {
mDialogReceiverFromClient.onDialogDismissed(reason);
if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
- onError(FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED,
+ onError(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
0 /* vendorCode */);
}
mDialogDismissed = true;
} catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify dialog dismissed", e);
+ Slog.e(getLogTag(), "Unable to notify dialog dismissed", e);
}
stop(true /* initiatedByClient */);
}
*/
public abstract void onStop();
- public AuthenticationClient(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId,
+ public AuthenticationClient(Context context, Metrics metrics,
+ BiometricService.DaemonWrapper daemon, long halDeviceId, IBinder token,
+ BiometricService.ServiceListener listener, int targetUserId, int groupId, long opId,
boolean restricted, String owner, Bundle bundle,
IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) {
- super(context, halDeviceId, token, receiver, targetUserId, groupId, restricted, owner);
+ super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId,
+ restricted, owner);
mOpId = opId;
mBundle = bundle;
mDialogReceiverFromClient = dialogReceiver;
// If the dialog is showing, the client doesn't need to receive onAcquired messages.
if (mBundle != null) {
try {
- if (acquiredInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+ if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
mStatusBarService.onFingerprintHelp(
mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode));
}
return false; // acquisition continues
} catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending acquired message", e);
+ Slog.e(getLogTag(), "Remote exception when sending acquired message", e);
return true; // client failed
} finally {
// Good scans will keep the device awake
- if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+ if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
notifyUserActivity();
}
}
mStatusBarService.onFingerprintError(
mFingerprintManager.getErrorString(error, vendorCode));
} catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending error", e);
+ Slog.e(getLogTag(), "Remote exception when sending error", e);
}
}
return super.onError(error, vendorCode);
com.android.internal.R.string.fingerprint_not_recognized));
}
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to notify Authenticated:", e);
+ Slog.e(getLogTag(), "Failed to notify Authenticated:", e);
}
}
- IFingerprintServiceReceiver receiver = getReceiver();
- if (receiver != null) {
+ final BiometricService.ServiceListener listener = getListener();
+ if (listener != null) {
try {
- MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH,
- authenticated);
+ mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
if (!authenticated) {
- receiver.onAuthenticationFailed(getHalDeviceId());
+ listener.onAuthenticationFailed(getHalDeviceId());
} else {
if (DEBUG) {
- Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString()
+ Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString()
+ ", id=" + fingerId + ", gp=" + groupId + ")");
}
Fingerprint fp = !getIsRestricted()
? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId())
: null;
- receiver.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
+ listener.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
}
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify Authenticated:", e);
+ Slog.w(getLogTag(), "Failed to notify Authenticated:", e);
result = true; // client failed
}
} else {
result = true; // client not listening
}
if (!authenticated) {
- if (receiver != null) {
+ if (listener != null) {
vibrateError();
}
// allow system-defined limit of number of attempts before giving up
if (lockoutMode != LOCKOUT_NONE) {
try {
mInLockout = true;
- Slog.w(TAG, "Forcing lockout (fp driver code should do this!), mode(" +
+ Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" +
lockoutMode + ")");
stop(false);
int errorCode = lockoutMode == LOCKOUT_TIMED ?
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
// TODO: if the dialog is showing, this error should be delayed. On a similar
// note, AuthenticationClient should override onError and delay all other errors
// as well, if the dialog is showing
- receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
+ listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
// Send the lockout message to the system dialog
if (mBundle != null) {
mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */));
}
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify lockout:", e);
+ Slog.w(getLogTag(), "Failed to notify lockout:", e);
}
}
result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
} else {
- if (receiver != null) {
+ if (listener != null) {
vibrateSuccess();
}
result |= true; // we have a valid fingerprint, done
*/
@Override
public int start() {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "start authentication: no fingerprint HAL!");
- return ERROR_ESRCH;
- }
onStart();
try {
- final int result = daemon.authenticate(mOpId, getGroupId());
+ final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
if (result != 0) {
- Slog.w(TAG, "startAuthentication failed, result=" + result);
- MetricsLogger.histogram(getContext(), "fingeprintd_auth_start_error", result);
- onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
+ mMetricsLogger.histogram(mMetrics.tagAuthStartError(), result);
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
return result;
}
- if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
+ if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
// If authenticating with system dialog, show the dialog
if (mBundle != null) {
try {
mStatusBarService.showFingerprintDialog(mBundle, mDialogReceiver);
} catch (RemoteException e) {
- Slog.e(TAG, "Unable to show fingerprint dialog", e);
+ Slog.e(getLogTag(), "Unable to show fingerprint dialog", e);
}
}
} catch (RemoteException e) {
- Slog.e(TAG, "startAuthentication failed", e);
+ Slog.e(getLogTag(), "startAuthentication failed", e);
return ERROR_ESRCH;
}
return 0; // success
@Override
public int stop(boolean initiatedByClient) {
if (mAlreadyCancelled) {
- Slog.w(TAG, "stopAuthentication: already cancelled!");
+ Slog.w(getLogTag(), "stopAuthentication: already cancelled!");
return 0;
}
onStop();
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
- return ERROR_ESRCH;
- }
+
try {
- final int result = daemon.cancel();
+ final int result = getDaemonWrapper().cancel();
if (result != 0) {
- Slog.w(TAG, "stopAuthentication failed, result=" + result);
+ Slog.w(getLogTag(), "stopAuthentication failed, result=" + result);
return result;
}
- if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating");
+ if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is no longer authenticating");
} catch (RemoteException e) {
- Slog.e(TAG, "stopAuthentication failed", e);
+ Slog.e(getLogTag(), "stopAuthentication failed", e);
return ERROR_ESRCH;
} finally {
// If the user already cancelled authentication (via some interaction with the
try {
mStatusBarService.hideFingerprintDialog();
} catch (RemoteException e) {
- Slog.e(TAG, "Unable to hide fingerprint dialog", e);
+ Slog.e(getLogTag(), "Unable to hide fingerprint dialog", e);
}
}
}
+
mAlreadyCancelled = true;
return 0; // success
}
@Override
public boolean onEnrollResult(int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!");
+ if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for authenticate!");
return true; // Invalid for Authenticate
}
@Override
public boolean onRemoved(int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!");
+ if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for authenticate!");
return true; // Invalid for Authenticate
}
@Override
public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for authenticate!");
+ if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for authenticate!");
return true; // Invalid for Authenticate
}
}
--- /dev/null
+/*
+ * 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.common;
+
+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.IBiometricPromptReceiver;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.KeyStore;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+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.HashMap;
+import java.util.List;
+
+/**
+ * Abstract base class containing all of the business logic for biometric services, e.g.
+ * Fingerprint, Face, Iris.
+ *
+ * @hide
+ */
+public abstract class BiometricService 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 AppOpsManager mAppOps;
+ 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();
+
+ protected final ResetFailedAttemptsForUserRunnablle mResetFailedAttemptsForCurrentUserRunnable =
+ new ResetFailedAttemptsForUserRunnablle();
+ protected final H mHandler = new H();
+
+ private ClientMonitor mCurrentClient;
+ private ClientMonitor mPendingClient;
+ private PerformanceStats mPerformanceStats;
+ protected int mCurrentUserId = UserHandle.USER_NULL;
+ // 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 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 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();
+
+ /**
+ * @return Returns one of the {@link AppOpsManager} constants which pertains to the specific
+ * biometric service.
+ */
+ protected abstract int getAppOp();
+
+ /**
+ * Notifies clients that lockout has been reset.
+ */
+ protected abstract void notifyLockoutResetMonitors();
+
+ /**
+ * 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 class AuthenticationClientImpl extends AuthenticationClient {
+
+ public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
+ IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
+ boolean restricted, String owner, Bundle bundle,
+ IBiometricPromptReceiver dialogReceiver,
+ IStatusBarService statusBarService) {
+ super(context, getMetrics(), daemon, halDeviceId, token, listener,
+ targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver,
+ statusBarService);
+ }
+
+ @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 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) {
+ super(context, getMetrics(), daemon, halDeviceId, token, listener,
+ userId, groupId, cryptoToken, restricted, owner);
+ }
+
+ @Override
+ public void notifyUserActivity() {
+ userActivity();
+ }
+ }
+
+ protected 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);
+ }
+
+ public void setShouldNotifyUserActivity(boolean shouldNotify) {
+ mShouldNotify = shouldNotify;
+ }
+
+ @Override
+ public void notifyUserActivity() {
+ if (mShouldNotify) {
+ userActivity();
+ }
+ }
+ }
+
+ protected 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 {
+ void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining)
+ throws RemoteException;
+
+ void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
+ throws RemoteException;
+
+ void onAuthenticationSucceeded(long deviceId,
+ BiometricAuthenticator.BiometricIdentifier biometric, int userId)
+ throws RemoteException;
+
+ void onAuthenticationFailed(long deviceId)
+ throws RemoteException;
+
+ void onError(long deviceId, int error, int vendorCode)
+ throws RemoteException;
+
+ void onRemoved(long deviceId, int fingerId, int groupId, int remaining)
+ throws RemoteException;
+
+ void onEnumerated(long deviceId, int fingerId, int groupId, int remaining)
+ throws RemoteException;
+ }
+
+ /**
+ * Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor
+ * subclasses.
+ */
+ protected interface DaemonWrapper {
+ 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) 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)) {
+ 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) {
+ if (getLockoutResetIntent().equals(intent.getAction())) {
+ final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
+ resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
+ }
+ }
+ };
+
+ private final class ResetFailedAttemptsForUserRunnablle implements Runnable {
+ @Override
+ public void run() {
+ resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+ ActivityManager.getCurrentUser());
+ }
+ };
+
+ /**
+ * 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);
+ mContext = context;
+ 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);
+ handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /*vendorCode */);
+ }
+
+ 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(long deviceId, int biometricId, int groupId,
+ ArrayList<Byte> token) {
+ ClientMonitor client = mCurrentClient;
+ if (biometricId != 0) {
+ final byte[] byteToken = new byte[token.size()];
+ for (int i = 0; i < token.size(); i++) {
+ byteToken[i] = token.get(i);
+ }
+ KeyStore.getInstance().addAuthToken(byteToken);
+ }
+ if (client != null && client.onAuthenticated(biometricId, groupId)) {
+ removeClient(client);
+ }
+ if (biometricId != 0) {
+ mPerformanceStats.accept++;
+ } else {
+ mPerformanceStats.reject++;
+ }
+ }
+
+ protected void handleEnrollResult(long deviceId, int biometricId, int groupId, int remaining) {
+ ClientMonitor client = mCurrentClient;
+ if (client != null && client.onEnrollResult(biometricId, groupId, 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.
+ updateActiveGroup(groupId, 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(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(final long deviceId,
+ final int biometricId,
+ final int groupId,
+ final int remaining) {
+ if (DEBUG) Slog.w(getTag(), "Removed: fid=" + biometricId
+ + ", gid=" + groupId
+ + ", dev=" + deviceId
+ + ", rem=" + remaining);
+
+ ClientMonitor client = mCurrentClient;
+ if (client != null && client.onRemoved(biometricId, groupId, remaining)) {
+ removeClient(client);
+ // When the last biometric of a group is removed, update the authenticator id
+ if (!hasEnrolledBiometrics(groupId)) {
+ updateActiveGroup(groupId, 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();
+
+ 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;
+
+ 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();
+
+ 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) {
+ if (DEBUG) Slog.v(getTag(), "stop client " + client.getOwnerString());
+ client.stop(client.getToken() == token);
+ } else {
+ if (DEBUG) Slog.v(getTag(), "can't stop client "
+ + client.getOwnerString() + " since tokens don't match");
+ }
+ } 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) {
+ updateActiveGroup(client.getGroupId(), 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(errorCode, 0 /* vendorCode */)) {
+ Slog.w(getTag(), "Cannot send permanent lockout message to client");
+ }
+ return;
+ }
+ startClient(client, true /* initiatedByClient */);
+ }
+
+ /**
+ * 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 (mAppOps.noteOp(getAppOp(), uid, opPackageName) != AppOpsManager.MODE_ALLOWED) {
+ 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);
+ }
+
+ private 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) {
+ mCurrentClient = newClient;
+ if (DEBUG) Slog.v(getTag(), "starting client "
+ + newClient.getClass().getSuperclass().getSimpleName()
+ + "(" + newClient.getOwnerString() + ")"
+ + ", initiatedByClient = " + initiatedByClient);
+ notifyClientActiveCallbacks(true);
+
+ newClient.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);
+ }
+ }
+
+ /**
+ * @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;
+ }
+
+ 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);
+ }
+ }
+
+}
\ No newline at end of file
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
* 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.
+ * limitations under the License
*/
-package com.android.server.biometrics.fingerprint;
+package com.android.server.biometrics.common;
-import android.Manifest;
import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.biometrics.BiometricConstants;
import android.media.AudioAttributes;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Vibrator;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
+
import java.util.NoSuchElementException;
/**
- * Abstract base class for keeping track and dispatching events from fingerprint HAL to the
+ * Abstract base class for keeping track and dispatching events from the biometric's HAL to the
* the current client. Subclasses are responsible for coordinating the interaction with
- * fingerprint HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
+ * the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
*/
public abstract class ClientMonitor implements IBinder.DeathRecipient {
- protected static final String TAG = FingerprintService.TAG; // TODO: get specific name
- protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. See errno.h.
- protected static final boolean DEBUG = FingerprintService.DEBUG;
- private static final long[] DEFAULT_SUCCESS_VIBRATION_PATTERN = new long[] {0, 30};
+ protected static final int ERROR_ESRCH = 3; // Likely HAL is dead. See errno.h.
+ protected static final boolean DEBUG = BiometricService.DEBUG;
private static final AudioAttributes FINGERPRINT_SONFICATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build();
+
private final Context mContext;
private final long mHalDeviceId;
private final int mTargetUserId;
private final String mOwner;
private final VibrationEffect mSuccessVibrationEffect;
private final VibrationEffect mErrorVibrationEffect;
+ private final BiometricService.DaemonWrapper mDaemon;
+
private IBinder mToken;
- private IFingerprintServiceReceiver mReceiver;
+ private BiometricService.ServiceListener mListener;
+
+ protected final MetricsLogger mMetricsLogger;
+ protected final Metrics mMetrics;
+
protected boolean mAlreadyCancelled;
/**
- * @param context context of FingerprintService
- * @param halDeviceId the HAL device ID of the associated fingerprint hardware
+ * @param context context of BiometricService
+ * @param daemon interface to call back to a specific biometric's daemon
+ * @param halDeviceId the HAL device ID of the associated biometric hardware
* @param token a unique token for the client
- * @param receiver recipient of related events (e.g. authentication)
+ * @param listener recipient of related events (e.g. authentication)
* @param userId target user id for operation
* @param groupId groupId for the fingerprint set
- * @param restricted whether or not client has the {@link Manifest#MANAGE_FINGERPRINT}
+ * @param restricted whether or not client has the MANAGE_* permission
* permission
* @param owner name of the client that owns this
*/
- public ClientMonitor(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int userId, int groupId,boolean restricted,
- String owner) {
+ public ClientMonitor(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+ long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int userId,
+ int groupId, boolean restricted, String owner) {
mContext = context;
+ mMetrics = metrics;
+ mDaemon = daemon;
mHalDeviceId = halDeviceId;
mToken = token;
- mReceiver = receiver;
+ mListener = listener;
mTargetUserId = userId;
mGroupId = groupId;
mIsRestricted = restricted;
mOwner = owner;
mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ mMetricsLogger = new MetricsLogger();
try {
if (token != null) {
token.linkToDeath(this, 0);
}
} catch (RemoteException e) {
- Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
+ Slog.w(getLogTag(), "caught remote exception in linkToDeath: ", e);
}
}
+ protected String getLogTag() {
+ return mMetrics.logTag();
+ }
+
/**
- * Contacts fingerprint HAL to start the client.
+ * Contacts the biometric's HAL to start the client.
* @return 0 on success, errno from driver on failure
*/
public abstract int start();
/**
- * Contacts fingerprint HAL to stop the client.
+ * Contacts the biometric's HAL to stop the client.
* @param initiatedByClient whether the operation is at the request of a client
*/
public abstract int stop(boolean initiatedByClient);
*/
public abstract void notifyUserActivity();
- /**
- * Gets the fingerprint daemon from the cached state in the container class.
- */
- public abstract IBiometricsFingerprint getFingerprintDaemon();
-
// Event callbacks from driver. Inappropriate calls is flagged/logged by the
// respective client (e.g. enrolling shouldn't get authenticate events).
// All of these return 'true' if the operation is completed and it's ok to move
- // to the next client (e.g. authentication accepts or rejects a fingerprint).
+ // to the next client (e.g. authentication accepts or rejects a biometric).
public abstract boolean onEnrollResult(int fingerId, int groupId, int rem);
public abstract boolean onAuthenticated(int fingerId, int groupId);
public abstract boolean onRemoved(int fingerId, int groupId, int remaining);
public abstract boolean onEnumerationResult(int fingerId, int groupId, int remaining);
/**
- * Called when we get notification from fingerprint HAL that an image has been acquired.
+ * Called when we get notification from the biometric's HAL that an image has been acquired.
* Common to authenticate and enroll.
* @param acquiredInfo info about the current image acquisition
* @return true if client should be removed
*/
public boolean onAcquired(int acquiredInfo, int vendorCode) {
- if (mReceiver == null)
- return true; // client not connected
try {
- mReceiver.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
+ mListener.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
return false; // acquisition continues...
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to invoke sendAcquired:", e);
- return true; // client failed
+ Slog.w(getLogTag(), "Failed to invoke sendAcquired", e);
+ return true;
} finally {
// Good scans will keep the device awake
- if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+ if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
notifyUserActivity();
}
}
}
/**
- * Called when we get notification from fingerprint HAL that an error has occurred with the
+ * Called when we get notification from the biometric's HAL that an error has occurred with the
* current operation. Common to authenticate, enroll, enumerate and remove.
* @param error
* @return true if client should be removed
*/
public boolean onError(int error, int vendorCode) {
- if (mReceiver != null) {
- try {
- mReceiver.onError(getHalDeviceId(), error, vendorCode);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to invoke sendError:", e);
- }
+ try {
+ mListener.onError(getHalDeviceId(), error, vendorCode);
+ } catch (RemoteException e) {
+ Slog.w(getLogTag(), "Failed to invoke sendError", e);
}
return true; // errors always remove current client
}
mToken.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
// TODO: remove when duplicate call bug is found
- Slog.e(TAG, "destroy(): " + this + ":", new Exception("here"));
+ Slog.e(getLogTag(), "destroy(): " + this + ":", new Exception("here"));
}
mToken = null;
}
- mReceiver = null;
+ mListener = null;
}
@Override
public void binderDied() {
mToken = null;
- mReceiver = null;
- onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mListener = null;
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
}
@Override
protected void finalize() throws Throwable {
try {
if (mToken != null) {
- if (DEBUG) Slog.w(TAG, "removing leaked reference: " + mToken);
- onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ if (DEBUG) Slog.w(getLogTag(), "removing leaked reference: " + mToken);
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
}
} finally {
super.finalize();
return mOwner;
}
- public final IFingerprintServiceReceiver getReceiver() {
- return mReceiver;
+ public final BiometricService.ServiceListener getListener() {
+ return mListener;
+ }
+
+ public final BiometricService.DaemonWrapper getDaemonWrapper() {
+ return mDaemon;
}
public final boolean getIsRestricted() {
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
* 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.
+ * limitations under the License
*/
-package com.android.server.biometrics.fingerprint;
+package com.android.server.biometrics.common;
import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.biometrics.BiometricConstants;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.biometrics.fingerprint.FingerprintUtils;
import java.util.Arrays;
private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
private byte[] mCryptoToken;
- public EnrollClient(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int userId, int groupId, byte [] cryptoToken,
- boolean restricted, String owner) {
- super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
+ public EnrollClient(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+ long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int userId,
+ int groupId, byte[] cryptoToken, boolean restricted, String owner) {
+ super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
+ owner);
mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
}
@Override
public boolean onEnrollResult(int fingerId, int groupId, int remaining) {
if (groupId != getGroupId()) {
- Slog.w(TAG, "groupId != getGroupId(), groupId: " + groupId +
+ Slog.w(getLogTag(), "groupId != getGroupId(), groupId: " + groupId +
" getGroupId():" + getGroupId());
}
if (remaining == 0) {
+ // TODO: handle other biometrics
FingerprintUtils.getInstance().addFingerprintForUser(getContext(), fingerId,
getTargetUserId());
}
* @return true if we're done.
*/
private boolean sendEnrollResult(int fpId, int groupId, int remaining) {
- IFingerprintServiceReceiver receiver = getReceiver();
- if (receiver == null)
- return true; // client not listening
-
vibrateSuccess();
- MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_ENROLL);
+ mMetricsLogger.action(mMetrics.actionBiometricEnroll());
try {
- receiver.onEnrollResult(getHalDeviceId(), fpId, groupId, remaining);
+ getListener().onEnrollResult(getHalDeviceId(), fpId, groupId, remaining);
return remaining == 0;
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify EnrollResult:", e);
+ Slog.w(getLogTag(), "Failed to notify EnrollResult:", e);
return true;
}
}
@Override
public int start() {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "enroll: no fingerprint HAL!");
- return ERROR_ESRCH;
- }
final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
try {
- final int result = daemon.enroll(mCryptoToken, getGroupId(), timeout);
+ final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout);
if (result != 0) {
- Slog.w(TAG, "startEnroll failed, result=" + result);
- MetricsLogger.histogram(getContext(), "fingerprintd_enroll_start_error", result);
- onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ Slog.w(getLogTag(), "startEnroll failed, result=" + result);
+ mMetricsLogger.histogram(mMetrics.tagEnrollStartError(), result);
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
return result;
}
} catch (RemoteException e) {
- Slog.e(TAG, "startEnroll failed", e);
+ Slog.e(getLogTag(), "startEnroll failed", e);
}
return 0; // success
}
@Override
public int stop(boolean initiatedByClient) {
if (mAlreadyCancelled) {
- Slog.w(TAG, "stopEnroll: already cancelled!");
+ Slog.w(getLogTag(), "stopEnroll: already cancelled!");
return 0;
}
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "stopEnrollment: no fingerprint HAL!");
- return ERROR_ESRCH;
- }
+
try {
- final int result = daemon.cancel();
+ final int result = getDaemonWrapper().cancel();
if (result != 0) {
- Slog.w(TAG, "startEnrollCancel failed, result = " + result);
+ Slog.w(getLogTag(), "startEnrollCancel failed, result = " + result);
return result;
}
} catch (RemoteException e) {
- Slog.e(TAG, "stopEnrollment failed", e);
+ Slog.e(getLogTag(), "stopEnrollment failed", e);
}
if (initiatedByClient) {
- onError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, 0 /* vendorCode */);
+ onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
}
mAlreadyCancelled = true;
return 0;
@Override
public boolean onRemoved(int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "onRemoved() called for enroll!");
+ if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for enroll!");
return true; // Invalid for EnrollClient
}
@Override
public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for enroll!");
+ if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for enroll!");
return true; // Invalid for EnrollClient
}
@Override
public boolean onAuthenticated(int fingerId, int groupId) {
- if (DEBUG) Slog.w(TAG, "onAuthenticated() called for enroll!");
+ if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for enroll!");
return true; // Invalid for EnrollClient
}
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
* 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.
+ * limitations under the License
*/
-package com.android.server.biometrics.fingerprint;
+package com.android.server.biometrics.common;
import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.biometrics.BiometricConstants;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.internal.logging.MetricsLogger;
/**
* A class to keep track of the enumeration state for a given client.
*/
public abstract class EnumerateClient extends ClientMonitor {
- public EnumerateClient(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int groupId, int userId,
- boolean restricted, String owner) {
- super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
+ public EnumerateClient(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+ long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int groupId,
+ int userId, boolean restricted, String owner) {
+ super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
+ owner);
}
@Override
public int start() {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- // The fingerprint template ids will be removed when we get confirmation from the HAL
+ // The biometric template ids will be removed when we get confirmation from the HAL
try {
- final int result = daemon.enumerate();
+ final int result = getDaemonWrapper().enumerate();
if (result != 0) {
- Slog.w(TAG, "start enumerate for user " + getTargetUserId()
+ Slog.w(getLogTag(), "start enumerate for user " + getTargetUserId()
+ " failed, result=" + result);
- MetricsLogger.histogram(getContext(), "fingerprintd_enum_start_error", result);
- onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mMetricsLogger.histogram(mMetrics.tagEnumerateStartError(), result);
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
return result;
}
} catch (RemoteException e) {
- Slog.e(TAG, "startEnumeration failed", e);
+ Slog.e(getLogTag(), "startEnumeration failed", e);
}
return 0;
}
@Override
public int stop(boolean initiatedByClient) {
if (mAlreadyCancelled) {
- Slog.w(TAG, "stopEnumerate: already cancelled!");
+ Slog.w(getLogTag(), "stopEnumerate: already cancelled!");
return 0;
}
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "stopEnumeration: no fingerprint HAL!");
- return ERROR_ESRCH;
- }
+
try {
- final int result = daemon.cancel();
+ final int result = getDaemonWrapper().cancel();
if (result != 0) {
- Slog.w(TAG, "stop enumeration failed, result=" + result);
+ Slog.w(getLogTag(), "stop enumeration failed, result=" + result);
return result;
}
} catch (RemoteException e) {
- Slog.e(TAG, "stopEnumeration failed", e);
+ Slog.e(getLogTag(), "stopEnumeration failed", e);
return ERROR_ESRCH;
}
// We don't actually stop enumerate, but inform the client that the cancel operation
// succeeded so we can start the next operation.
if (initiatedByClient) {
- onError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, 0 /* vendorCode */);
+ onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
}
mAlreadyCancelled = true;
return 0; // success
@Override
public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
- IFingerprintServiceReceiver receiver = getReceiver();
- if (receiver == null)
- return true; // client not listening
try {
- receiver.onEnumerated(getHalDeviceId(), fingerId, groupId, remaining);
+ getListener().onEnumerated(getHalDeviceId(), fingerId, groupId, remaining);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify enumerated:", e);
+ Slog.w(getLogTag(), "Failed to notify enumerated:", e);
}
return remaining == 0;
}
@Override
public boolean onAuthenticated(int fingerId, int groupId) {
- if (DEBUG) Slog.w(TAG, "onAuthenticated() called for enumerate!");
+ if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for enumerate!");
return true; // Invalid for Enumerate.
}
@Override
public boolean onEnrollResult(int fingerId, int groupId, int rem) {
- if (DEBUG) Slog.w(TAG, "onEnrollResult() called for enumerate!");
+ if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for enumerate!");
return true; // Invalid for Enumerate.
}
@Override
public boolean onRemoved(int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "onRemoved() called for enumerate!");
+ if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for enumerate!");
return true; // Invalid for Enumerate.
}
}
--- /dev/null
+/*
+ * 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.common;
+
+public interface Metrics {
+ /** The log tag */
+ String logTag();
+
+ /** Strings for MetricsLogger.count() and MetricsLogger.histogram() */
+ String tagHalDied();
+ String tagAuthToken();
+ String tagAuthStartError();
+ String tagEnrollStartError();
+ String tagEnumerateStartError();
+ String tagRemoveStartError();
+
+ /** Integers for MetricsLogger.action() */
+ int actionBiometricAuth();
+ int actionBiometricEnroll();
+}
\ No newline at end of file
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
* 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.
+ * limitations under the License
*/
-package com.android.server.biometrics.fingerprint;
+package com.android.server.biometrics.common;
import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.biometrics.BiometricConstants;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.internal.logging.MetricsLogger;
+
+import com.android.server.biometrics.fingerprint.FingerprintUtils;
/**
* A class to keep track of the remove state for a given client.
public abstract class RemovalClient extends ClientMonitor {
private int mFingerId;
- public RemovalClient(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int fingerId, int groupId, int userId,
- boolean restricted, String owner) {
- super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
+ public RemovalClient(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+ long halDeviceId, IBinder token, BiometricService.ServiceListener listener,
+ int fingerId, int groupId, int userId, boolean restricted, String owner) {
+ super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
+ owner);
mFingerId = fingerId;
}
@Override
public int start() {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- // The fingerprint template ids will be removed when we get confirmation from the HAL
+ // The biometric template ids will be removed when we get confirmation from the HAL
try {
- final int result = daemon.remove(getGroupId(), mFingerId);
+ final int result = getDaemonWrapper().remove(getGroupId(), mFingerId);
if (result != 0) {
- Slog.w(TAG, "startRemove with id = " + mFingerId + " failed, result=" + result);
- MetricsLogger.histogram(getContext(), "fingerprintd_remove_start_error", result);
- onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ Slog.w(getLogTag(), "startRemove with id = " + mFingerId + " failed, result=" +
+ result);
+ mMetricsLogger.histogram(mMetrics.tagRemoveStartError(), result);
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
return result;
}
} catch (RemoteException e) {
- Slog.e(TAG, "startRemove failed", e);
+ Slog.e(getLogTag(), "startRemove failed", e);
}
return 0;
}
@Override
public int stop(boolean initiatedByClient) {
if (mAlreadyCancelled) {
- Slog.w(TAG, "stopRemove: already cancelled!");
+ Slog.w(getLogTag(), "stopRemove: already cancelled!");
return 0;
}
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "stopRemoval: no fingerprint HAL!");
- return ERROR_ESRCH;
- }
+
try {
- final int result = daemon.cancel();
+ final int result = getDaemonWrapper().cancel();
if (result != 0) {
- Slog.w(TAG, "stopRemoval failed, result=" + result);
+ Slog.w(getLogTag(), "stopRemoval failed, result=" + result);
return result;
}
- if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer removing");
+ if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is no longer removing");
} catch (RemoteException e) {
- Slog.e(TAG, "stopRemoval failed", e);
+ Slog.e(getLogTag(), "stopRemoval failed", e);
return ERROR_ESRCH;
}
mAlreadyCancelled = true;
* @return true if we're done.
*/
private boolean sendRemoved(int fingerId, int groupId, int remaining) {
- IFingerprintServiceReceiver receiver = getReceiver();
try {
- if (receiver != null) {
- receiver.onRemoved(getHalDeviceId(), fingerId, groupId, remaining);
- }
+ getListener().onRemoved(getHalDeviceId(), fingerId, groupId, remaining);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify Removed:", e);
+ Slog.w(getLogTag(), "Failed to notify Removed:", e);
}
return remaining == 0;
}
@Override
public boolean onRemoved(int fingerId, int groupId, int remaining) {
if (fingerId != 0) {
+ // TODO: biometric
FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(), fingerId,
getTargetUserId());
}
@Override
public boolean onEnrollResult(int fingerId, int groupId, int rem) {
- if (DEBUG) Slog.w(TAG, "onEnrollResult() called for remove!");
+ if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for remove!");
return true; // Invalid for Remove
}
@Override
public boolean onAuthenticated(int fingerId, int groupId) {
- if (DEBUG) Slog.w(TAG, "onAuthenticated() called for remove!");
+ if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for remove!");
return true; // Invalid for Remove.
}
@Override
public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for remove!");
+ if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for remove!");
return true; // Invalid for Remove.
}
-
-
}
--- /dev/null
+/*
+ * 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.fingerprint;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.server.biometrics.common.Metrics;
+
+public class FingerprintMetrics implements Metrics {
+
+ @Override
+ public String logTag() {
+ return FingerprintService.TAG;
+ }
+
+ @Override
+ public String tagHalDied() {
+ return "fingerprintd_died";
+ }
+
+ @Override
+ public String tagAuthToken() {
+ return "fingerprint_token";
+ }
+
+ @Override
+ public String tagAuthStartError() {
+ return "fingerprintd_auth_start_error";
+ }
+
+ @Override
+ public String tagEnrollStartError() {
+ return "fingerprintd_enroll_start_error";
+ }
+
+ @Override
+ public String tagEnumerateStartError() {
+ return "fingerprintd_enum_start_error";
+ }
+
+ @Override
+ public String tagRemoveStartError() {
+ return "fingerprintd_remove_start_error";
+ }
+
+ @Override
+ public int actionBiometricAuth() {
+ return MetricsProto.MetricsEvent.ACTION_FINGERPRINT_AUTH;
+ }
+
+ @Override
+ public int actionBiometricEnroll() {
+ return MetricsProto.MetricsEvent.ACTION_FINGERPRINT_ENROLL;
+ }
+}
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.app.ActivityTaskManager;
-import android.app.AlarmManager;
import android.app.AppOpsManager;
-import android.app.IActivityManager;
-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.IBiometricPromptReceiver;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Environment;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.IHwBinder;
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.os.SELinux;
import android.os.ServiceManager;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.security.KeyStore;
import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
-import com.android.server.SystemService;
+import com.android.server.biometrics.common.BiometricService;
+import com.android.server.biometrics.common.ClientMonitor;
+import com.android.server.biometrics.common.EnumerateClient;
+import com.android.server.biometrics.common.Metrics;
import org.json.JSONArray;
import org.json.JSONException;
*
* @hide
*/
-public class FingerprintService extends SystemService implements IHwBinder.DeathRecipient {
- static final String TAG = "FingerprintService";
- static final boolean DEBUG = true;
+public class FingerprintService extends BiometricService {
+
+ protected static final String TAG = "FingerprintService";
+ private static final boolean DEBUG = true;
private static final boolean CLEANUP_UNUSED_FP = true;
private static final String FP_DATA_DIR = "fpdata";
- private static final int MSG_USER_SWITCHING = 10;
private static final String ACTION_LOCKOUT_RESET =
"com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET";
- private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
-
- private class PerformanceStats {
- int accept; // number of accepted fingerprints
- int reject; // number of rejected fingerprints
- 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.)
- int lockout; // total number of lockouts
- int permanentLockout; // total number of permanent lockouts
- }
-
- private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors =
- new ArrayList<>();
- private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
- new CopyOnWriteArrayList<>();
- private final Map<Integer, Long> mAuthenticatorIds =
- Collections.synchronizedMap(new HashMap<>());
- private final AppOpsManager mAppOps;
- private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;
- private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
- private final String mKeyguardPackage;
- private int mCurrentUserId = UserHandle.USER_NULL;
- private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
- private Context mContext;
- private long mHalDeviceId;
- private SparseBooleanArray mTimedLockoutCleared;
- private SparseIntArray mFailedAttempts;
- @GuardedBy("this")
- private IBiometricsFingerprint mDaemon;
- private IStatusBarService mStatusBarService;
- private final IActivityManager mActivityManager;
- private final IActivityTaskManager mActivityTaskManager;
- private final PowerManager mPowerManager;
- private final AlarmManager mAlarmManager;
- private final UserManager mUserManager;
- private ClientMonitor mCurrentClient;
- private ClientMonitor mPendingClient;
- private PerformanceStats mPerformanceStats;
-
- private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
- private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
-
- private class UserFingerprint {
+ private final class UserFingerprint {
Fingerprint f;
int userId;
public UserFingerprint(Fingerprint f, int userId) {
}
}
- // Normal fingerprint authentications are tracked by mPerformanceMap.
- private HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
-
- // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap.
- private HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>();
-
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case MSG_USER_SWITCHING:
- handleUserSwitching(msg.arg1);
- break;
-
- default:
- Slog.w(TAG, "Unknown message:" + msg.what);
- }
- }
- };
-
- private final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
- final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
- resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
- }
- }
- };
+ /**
+ * Receives the incoming binder calls from FingerprintManager.
+ */
+ private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
- private final Runnable mResetFailedAttemptsForCurrentUserRunnable = new Runnable() {
- @Override
- public void run() {
- resetFailedAttemptsForUser(true /* clearAttemptCounter */,
- ActivityManager.getCurrentUser());
- }
- };
+ /**
+ * The following methods contain common code which is shared in biometrics/common.
+ */
- private final Runnable mResetClientState = new 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 FingerprintManager#FINGERPRINT_ERROR_CANCEL} in response to cancel()
- // once it has successfully switched to the IDLE state in the fingerprint HAL.
- // Additionally,{@link FingerprintManager#FINGERPRINT_ERROR_CANCEL} should only be sent
- // in response to an actual cancel() call.
- Slog.w(TAG, "Client "
- + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
- + " failed to respond to cancel, starting client "
- + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
-
- mCurrentClient = null;
- startClient(mPendingClient, false);
+ @Override // Binder call
+ public long preEnroll(IBinder token) {
+ checkPermission(MANAGE_FINGERPRINT);
+ return startPreEnroll(token);
}
- };
- private final TaskStackListener mTaskStackListener = new 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)) {
- Slog.e(TAG, "Stopping background authentication, top: " + topPackage
- + " currentClient: " + currentClient);
- mCurrentClient.stop(false /* initiatedByClient */);
- }
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to get running tasks", e);
- }
+ @Override // Binder call
+ public int postEnroll(IBinder token) {
+ checkPermission(MANAGE_FINGERPRINT);
+ return startPostEnroll(token);
}
- };
-
- public FingerprintService(Context context) {
- super(context);
- mContext = context;
- mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString(
- com.android.internal.R.string.config_keyguardComponent)).getPackageName();
- mAppOps = context.getSystemService(AppOpsManager.class);
- mPowerManager = mContext.getSystemService(PowerManager.class);
- mAlarmManager = mContext.getSystemService(AlarmManager.class);
- mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
- RESET_FINGERPRINT_LOCKOUT, null /* handler */);
- mUserManager = UserManager.get(mContext);
- mTimedLockoutCleared = new SparseBooleanArray();
- mFailedAttempts = new SparseIntArray();
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- mActivityManager = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
- .getService();
- mActivityTaskManager = ((ActivityTaskManager) context.getSystemService(
- Context.ACTIVITY_TASK_SERVICE)).getService();
- }
- @Override
- public void serviceDied(long cookie) {
- Slog.v(TAG, "fingerprint HAL died");
- MetricsLogger.count(mContext, "fingerprintd_died", 1);
- handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /*vendorCode */);
- }
+ @Override // Binder call
+ public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
+ final IFingerprintServiceReceiver receiver, final int flags,
+ final String opPackageName) {
+ checkPermission(MANAGE_FINGERPRINT);
- public synchronized IBiometricsFingerprint getFingerprintDaemon() {
- if (mDaemon == null) {
- Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
- try {
- mDaemon = IBiometricsFingerprint.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened. Logged below.
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get biometric interface", e);
- }
- if (mDaemon == null) {
- Slog.w(TAG, "fingerprint HIDL not available");
- return null;
- }
+ final boolean restricted = isRestricted();
+ final int groupId = userId; // default group for fingerprint enrollment
+ final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
+ mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,
+ cryptoToken, restricted, opPackageName);
- mDaemon.asBinder().linkToDeath(this, 0);
- try {
- mHalDeviceId = mDaemon.setNotify(mDaemonCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to open fingerprint HAL", e);
- mDaemon = null; // try again later!
- }
+ enrollInternal(client, userId);
+ }
- if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId);
- if (mHalDeviceId != 0) {
- loadAuthenticatorIds();
- updateActiveGroup(ActivityManager.getCurrentUser(), null);
- doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
- } else {
- Slog.w(TAG, "Failed to open Fingerprint HAL!");
- MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
- mDaemon = null;
- }
+ @Override // Binder call
+ public void cancelEnrollment(final IBinder token) {
+ checkPermission(MANAGE_FINGERPRINT);
+ cancelEnrollmentInternal(token);
}
- return mDaemon;
- }
- /** Populates existing authenticator ids. To be used only during the start of the service. */
- private 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(mContext).getUsers(true /* excludeDying */)) {
- int userId = getUserOrWorkProfileId(null, user.id);
- if (!mAuthenticatorIds.containsKey(userId)) {
- updateActiveGroup(userId, null);
- }
+ @Override // Binder call
+ public void authenticate(final IBinder token, final long opId, final int groupId,
+ final IFingerprintServiceReceiver receiver, final int flags,
+ final String opPackageName, final Bundle bundle,
+ final IBiometricPromptReceiver dialogReceiver) {
+ final boolean restricted = isRestricted();
+ final AuthenticationClientImpl client = new AuthenticationClientImpl(getContext(),
+ mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
+ mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
+ dialogReceiver, mStatusBarService) {
+
+ };
+ authenticateInternal(client, opId, opPackageName);
}
- t = System.currentTimeMillis() - t;
- if (t > 1000) {
- Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
+ @Override // Binder call
+ public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ cancelAuthenticationInternal(token, opPackageName);
}
- }
- /**
- * This method should be called upon connection to the daemon, and when user switches.
- * @param userId
- */
- private void doFingerprintCleanupForUser(int userId) {
- if (CLEANUP_UNUSED_FP) {
- enumerateUser(userId);
+ @Override // Binder call
+ public void setActiveUser(final int userId) {
+ checkPermission(MANAGE_FINGERPRINT);
+ setActiveUserInternal(userId);
}
- }
- private void clearEnumerateState() {
- if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
- mUnknownFingerprints.clear();
- }
+ @Override // Binder call
+ public void remove(final IBinder token, final int fingerId, final int groupId,
+ final int userId, final IFingerprintServiceReceiver receiver) {
+ checkPermission(MANAGE_FINGERPRINT);
- private void enumerateUser(int userId) {
- if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
- boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
- startEnumerate(mToken, userId, null, restricted, true /* internal */);
- }
+ if (token == null) {
+ Slog.w(TAG, "remove(): token is null");
+ return;
+ }
- // Remove unknown fingerprints from hardware
- private void cleanupUnknownFingerprints() {
- if (!mUnknownFingerprints.isEmpty()) {
- UserFingerprint uf = mUnknownFingerprints.get(0);
- mUnknownFingerprints.remove(uf);
- boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
- startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
- restricted, true /* internal */);
- } else {
- clearEnumerateState();
+ final boolean restricted = isRestricted();
+ final RemovalClientImpl client = new RemovalClientImpl(getContext(), mDaemonWrapper,
+ mHalDeviceId, token, new ServiceListenerImpl(receiver), fingerId, groupId,
+ userId, restricted, token.toString());
+ client.setShouldNotifyUserActivity(true);
+ removeInternal(client);
}
- }
- protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- ClientMonitor client = mCurrentClient;
+ @Override // Binder call
+ public void enumerate(final IBinder token, final int userId,
+ final IFingerprintServiceReceiver receiver) {
+ checkPermission(MANAGE_FINGERPRINT);
- if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
- return;
+ final boolean restricted = isRestricted();
+ final EnumerateClientImpl client = new EnumerateClientImpl(getContext(), mDaemonWrapper,
+ mHalDeviceId, token, new ServiceListenerImpl(receiver), userId, userId,
+ restricted, getContext().getOpPackageName());
+ enumerateInternal(client);
}
- client.onEnumerationResult(fingerId, groupId, remaining);
- // All fingerprints in hardware for this user were enumerated
- if (remaining == 0) {
- if (client instanceof InternalEnumerateClient) {
- List<Fingerprint> unknownFingerprints =
- ((InternalEnumerateClient) client).getUnknownFingerprints();
+ @Override // Binder call
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+ return;
+ }
- if (!unknownFingerprints.isEmpty()) {
- Slog.w(TAG, "Adding " + unknownFingerprints.size() +
- " fingerprints for deletion");
- }
- for (Fingerprint f : unknownFingerprints) {
- mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (args.length > 0 && "--proto".equals(args[0])) {
+ dumpProto(fd);
+ } else {
+ dumpInternal(pw);
}
- removeClient(client);
- cleanupUnknownFingerprints();
- } else {
- removeClient(client);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
- }
- protected void handleError(long deviceId, int error, int vendorCode) {
- ClientMonitor client = mCurrentClient;
- if (client instanceof InternalRemovalClient || client instanceof InternalEnumerateClient) {
- clearEnumerateState();
- }
- if (client != null && client.onError(error, vendorCode)) {
- removeClient(client);
- }
+ /**
+ * The following methods don't use any common code from BiometricService
+ */
- if (DEBUG) Slog.v(TAG, "handleError(client="
- + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
- // This is the magic code that starts the next client when the old client finishes.
- if (error == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
- mHandler.removeCallbacks(mResetClientState);
- if (mPendingClient != null) {
- if (DEBUG) Slog.v(TAG, "start pending client " + mPendingClient.getOwnerString());
- startClient(mPendingClient, false);
- mPendingClient = null;
+ @Override // Binder call
+ public boolean isHardwareDetected(long deviceId, String opPackageName) {
+ if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.getCallingUserId())) {
+ return false;
}
- } else if (error == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
- // If we get HW_UNAVAILABLE, try to connect again later...
- Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
- synchronized (this) {
- mDaemon = null;
- mHalDeviceId = 0;
- mCurrentUserId = UserHandle.USER_NULL;
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ return daemon != null && mHalDeviceId != 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
- }
- protected void handleRemoved(long deviceId, int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "Removed: fid=" + fingerId
- + ", gid=" + groupId
- + ", dev=" + deviceId
- + ", rem=" + remaining);
-
- ClientMonitor client = mCurrentClient;
- if (client != null && client.onRemoved(fingerId, groupId, remaining)) {
- removeClient(client);
- // When the last fingerprint of a group is removed, update the authenticator id
- if (!hasEnrolledFingerprints(groupId)) {
- updateActiveGroup(groupId, null);
+ @Override // Binder call
+ public void rename(final int fingerId, final int groupId, final String name) {
+ checkPermission(MANAGE_FINGERPRINT);
+ if (!isCurrentUserOrProfile(groupId)) {
+ return;
}
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mFingerprintUtils.renameFingerprintForUser(mContext, fingerId,
+ groupId, name);
+ }
+ });
}
- if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
- cleanupUnknownFingerprints();
- } else if (client instanceof InternalRemovalClient){
- clearEnumerateState();
- }
- }
- protected void handleAuthenticated(long deviceId, int fingerId, int groupId,
- ArrayList<Byte> token) {
- ClientMonitor client = mCurrentClient;
- if (fingerId != 0) {
- // Ugh...
- final byte[] byteToken = new byte[token.size()];
- for (int i = 0; i < token.size(); i++) {
- byteToken[i] = token.get(i);
+ @Override // Binder call
+ public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) {
+ if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.getCallingUserId())) {
+ return Collections.emptyList();
}
- // Send to Keystore
- KeyStore.getInstance().addAuthToken(byteToken);
- }
- if (client != null && client.onAuthenticated(fingerId, groupId)) {
- removeClient(client);
- }
- if (fingerId != 0) {
- mPerformanceStats.accept++;
- } else {
- mPerformanceStats.reject++;
- }
- }
- 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++;
+ return FingerprintService.this.getEnrolledFingerprints(userId);
}
- }
- protected void handleEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
- ClientMonitor client = mCurrentClient;
- if (client != null && client.onEnrollResult(fingerId, groupId, 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 fingerprint is enrolled.
- updateActiveGroup(groupId, null);
+ @Override // Binder call
+ public boolean hasEnrolledFingerprints(int userId, String opPackageName) {
+ if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.getCallingUserId())) {
+ return false;
+ }
+
+ return FingerprintService.this.hasEnrolledBiometrics(userId);
}
- }
-
- private void userActivity() {
- long now = SystemClock.uptimeMillis();
- mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
- }
-
- void handleUserSwitching(int userId) {
- if (mCurrentClient instanceof InternalRemovalClient
- || mCurrentClient instanceof InternalEnumerateClient) {
- Slog.w(TAG, "User switched while performing cleanup");
- removeClient(mCurrentClient);
- clearEnumerateState();
- }
- updateActiveGroup(userId, null);
- doFingerprintCleanupForUser(userId);
- }
-
- private void removeClient(ClientMonitor client) {
- if (client != null) {
- client.destroy();
- if (client != mCurrentClient && mCurrentClient != null) {
- Slog.w(TAG, "Unexpected client: " + client.getOwnerString() + "expected: "
- + mCurrentClient != null ? mCurrentClient.getOwnerString() : "null");
- }
- }
- if (mCurrentClient != null) {
- if (DEBUG) Slog.v(TAG, "Done with client: " + client.getOwnerString());
- mCurrentClient = null;
- }
- if (mPendingClient == null) {
- notifyClientActiveCallbacks(false);
- }
- }
-
- private int getLockoutMode() {
- final int currentUser = ActivityManager.getCurrentUser();
- final int failedAttempts = mFailedAttempts.get(currentUser, 0);
- if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
- return AuthenticationClient.LOCKOUT_PERMANENT;
- } else if (failedAttempts > 0 &&
- mTimedLockoutCleared.get(currentUser, false) == false
- && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
- return AuthenticationClient.LOCKOUT_TIMED;
- }
- return AuthenticationClient.LOCKOUT_NONE;
- }
-
- private void scheduleLockoutResetForUser(int userId) {
- mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
- getLockoutResetIntentForUser(userId));
- }
-
- private void cancelLockoutResetForUser(int userId) {
- mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
- }
-
- private PendingIntent getLockoutResetIntentForUser(int userId) {
- return PendingIntent.getBroadcast(mContext, userId,
- new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public long startPreEnroll(IBinder token) {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "startPreEnroll: no fingerprint HAL!");
- return 0;
- }
- try {
- return daemon.preEnroll();
- } catch (RemoteException e) {
- Slog.e(TAG, "startPreEnroll failed", e);
- }
- return 0;
- }
-
- public int startPostEnroll(IBinder token) {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "startPostEnroll: no fingerprint HAL!");
- return 0;
- }
- try {
- return daemon.postEnroll();
- } catch (RemoteException e) {
- Slog.e(TAG, "startPostEnroll failed", e);
- }
- return 0;
- }
-
- /**
- * Calls fingerprint 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 FingerprintManager#FINGERPRINT_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(TAG, "request stop current client " + currentClient.getOwnerString());
- if (currentClient instanceof InternalEnumerateClient ||
- currentClient instanceof InternalRemovalClient) {
- // 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(TAG, "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) {
- mCurrentClient = newClient;
- if (DEBUG) Slog.v(TAG, "starting client "
- + newClient.getClass().getSuperclass().getSimpleName()
- + "(" + newClient.getOwnerString() + ")"
- + ", initiatedByClient = " + initiatedByClient);
- notifyClientActiveCallbacks(true);
-
- newClient.start();
- }
- }
-
- void startRemove(IBinder token, int fingerId, int groupId, int userId,
- IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
- if (token == null) {
- Slog.w(TAG, "startRemove: token is null");
- return;
- }
- if (receiver == null) {
- Slog.w(TAG, "startRemove: receiver is null");
- return;
- }
-
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "startRemove: no fingerprint HAL!");
- return;
- }
-
- if (internal) {
- Context context = getContext();
- InternalRemovalClient client = new InternalRemovalClient(context, mHalDeviceId,
- token, receiver, fingerId, groupId, userId, restricted,
- context.getOpPackageName()) {
- @Override
- public void notifyUserActivity() {
-
- }
- @Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
- }
- };
- startClient(client, true);
- }
- else {
- RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
- receiver, fingerId, groupId, userId, restricted, token.toString()) {
- @Override
- public void notifyUserActivity() {
- FingerprintService.this.userActivity();
- }
- @Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
- }
- };
- startClient(client, true);
- }
- }
+ @Override // Binder call
+ public long getAuthenticatorId(String opPackageName) {
+ // In this method, we're not checking whether the caller is permitted to use fingerprint
+ // API because current authenticator ID is leaked (in a more contrived way) via Android
+ // Keystore (android.security.keystore package): the user of that API can create a key
+ // which requires fingerprint authentication for its use, and then query the key's
+ // characteristics (hidden API) which returns, among other things, fingerprint
+ // authenticator ID which was active at key creation time.
+ //
+ // Reason: The part of Android Keystore which runs inside an app's process invokes this
+ // method in certain cases. Those cases are not always where the developer demonstrates
+ // explicit intent to use fingerprint functionality. Thus, to avoiding throwing an
+ // unexpected SecurityException this method does not check whether its caller is
+ // permitted to use fingerprint API.
+ //
+ // The permission check should be restored once Android Keystore no longer invokes this
+ // method from inside app processes.
- void startEnumerate(IBinder token, int userId,
- IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.w(TAG, "startEnumerate: no fingerprint HAL!");
- return;
+ return FingerprintService.this.getAuthenticatorId(opPackageName);
}
- if (internal) {
- List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
- Context context = getContext();
- InternalEnumerateClient client = new InternalEnumerateClient(context, mHalDeviceId,
- token, receiver, userId, userId, restricted, context.getOpPackageName(),
- enrolledList) {
- @Override
- public void notifyUserActivity() {
-
- }
- @Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
- }
- };
- startClient(client, true);
+ @Override // Binder call
+ public void resetTimeout(byte [] token) {
+ checkPermission(RESET_FINGERPRINT_LOCKOUT);
+ // TODO: confirm security token when we move timeout management into the HAL layer.
+ mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
}
- else {
- EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token,
- receiver, userId, userId, restricted, token.toString()) {
- @Override
- public void notifyUserActivity() {
- FingerprintService.this.userActivity();
- }
+ @Override
+ public void addLockoutResetCallback(final IFingerprintServiceLockoutResetCallback callback)
+ throws RemoteException {
+ mHandler.post(new Runnable() {
@Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
- }
- };
- startClient(client, true);
- }
- }
-
- public List<Fingerprint> getEnrolledFingerprints(int userId) {
- return mFingerprintUtils.getFingerprintsForUser(mContext, userId);
- }
-
- public boolean hasEnrolledFingerprints(int userId) {
- if (userId != UserHandle.getCallingUserId()) {
- checkPermission(INTERACT_ACROSS_USERS);
- }
- return mFingerprintUtils.getFingerprintsForUser(mContext, userId).size() > 0;
- }
-
- boolean hasPermission(String permission) {
- return getContext().checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- void checkPermission(String permission) {
- getContext().enforceCallingOrSelfPermission(permission,
- "Must have " + permission + " permission.");
- }
-
- 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(TAG, "Unable to acquire UserManager");
- }
- return userId;
- }
-
- boolean isCurrentUserOrProfile(int userId) {
- UserManager um = UserManager.get(mContext);
- if (um == null) {
- Slog.e(TAG, "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;
+ public void run() {
+ addLockoutResetMonitor(
+ new FingerprintServiceLockoutResetMonitor(callback));
}
- }
- } finally {
- Binder.restoreCallingIdentity(token);
+ });
}
- return false;
- }
-
- private boolean isForegroundActivity(int uid, int pid) {
- try {
- List<RunningAppProcessInfo> procs =
- ActivityManager.getService().getRunningAppProcesses();
- int N = procs.size();
- for (int i = 0; i < N; i++) {
- RunningAppProcessInfo proc = procs.get(i);
- if (proc.pid == pid && proc.uid == uid
- && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
- return true;
- }
+ @Override
+ public boolean isClientActive() {
+ checkPermission(MANAGE_FINGERPRINT);
+ synchronized(FingerprintService.this) {
+ return (getCurrentClient() != null) || (getPendingClient() != null);
}
- } catch (RemoteException e) {
- Slog.w(TAG, "am.getRunningAppProcesses() failed");
}
- return false;
- }
- /**
- * @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 fingerprint API
- */
- private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid,
- int pid, int userId) {
- if (getContext().checkCallingPermission(USE_FINGERPRINT)
- != PackageManager.PERMISSION_GRANTED) {
- checkPermission(USE_BIOMETRIC);
+ @Override
+ public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
+ checkPermission(MANAGE_FINGERPRINT);
+ mClientActiveCallbacks.add(callback);
}
- if (isKeyguard(opPackageName)) {
- return true; // Keyguard is always allowed
- }
- if (!isCurrentUserOrProfile(userId)) {
- Slog.w(TAG,"Rejecting " + opPackageName + " ; not a current user or profile");
- return false;
- }
- if (mAppOps.noteOp(AppOpsManager.OP_USE_FINGERPRINT, uid, opPackageName)
- != AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Rejecting " + opPackageName + " ; permission denied");
- return false;
- }
- if (requireForeground && !(isForegroundActivity(uid, pid) || currentClient(opPackageName))){
- Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground");
- return false;
+ @Override
+ public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
+ checkPermission(MANAGE_FINGERPRINT);
+ mClientActiveCallbacks.remove(callback);
}
- return true;
- }
-
- /**
- * @param opPackageName package of the caller
- * @return true if this is the same client currently using fingerprint
- */
- private boolean currentClient(String opPackageName) {
- return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName);
}
/**
- * @param clientPackage
- * @return true if this is keyguard package
+ * Receives callbacks from the ClientMonitor implementations. The results are forwarded to
+ * the FingerprintManager.
*/
- private boolean isKeyguard(String clientPackage) {
- return mKeyguardPackage.equals(clientPackage);
- }
+ private class ServiceListenerImpl implements ServiceListener {
- private void addLockoutResetMonitor(FingerprintServiceLockoutResetMonitor monitor) {
- if (!mLockoutMonitors.contains(monitor)) {
- mLockoutMonitors.add(monitor);
- }
- }
-
- private void removeLockoutResetCallback(
- FingerprintServiceLockoutResetMonitor monitor) {
- mLockoutMonitors.remove(monitor);
- }
+ private IFingerprintServiceReceiver mFingerprintServiceReceiver;
- private void notifyLockoutResetMonitors() {
- for (int i = 0; i < mLockoutMonitors.size(); i++) {
- mLockoutMonitors.get(i).sendLockoutReset();
+ public ServiceListenerImpl(IFingerprintServiceReceiver receiver) {
+ mFingerprintServiceReceiver = receiver;
}
- }
- private void notifyClientActiveCallbacks(boolean isActive) {
- List<IFingerprintClientActiveCallback> callbacks = mClientActiveCallbacks;
- for (int i = 0; i < callbacks.size(); i++) {
- try {
- callbacks.get(i).onClientActiveChanged(isActive);
- } catch (RemoteException re) {
- // If the remote is dead, stop notifying it
- mClientActiveCallbacks.remove(callbacks.get(i));
+ @Override
+ public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining)
+ throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onEnrollResult(deviceId, fingerId, groupId, remaining);
}
}
- }
-
- private void startAuthentication(IBinder token, long opId, int callingUserId, int groupId,
- IFingerprintServiceReceiver receiver, int flags, boolean restricted,
- String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver) {
- updateActiveGroup(groupId, opPackageName);
-
- if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")");
-
- AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token,
- receiver, mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
- dialogReceiver, mStatusBarService) {
- @Override
- public void onStart() {
- try {
- mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not register task stack listener", e);
- }
- }
-
- @Override
- public void onStop() {
- try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not unregister task stack listener", e);
- }
- }
-
- @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;
- }
-
- @Override
- public void resetFailedAttempts() {
- FingerprintService.this.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
- ActivityManager.getCurrentUser());
+ @Override
+ public void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
+ throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onAcquired(deviceId, acquiredInfo, vendorCode);
}
+ }
- @Override
- public void notifyUserActivity() {
- FingerprintService.this.userActivity();
+ @Override
+ public void onAuthenticationSucceeded(long deviceId,
+ BiometricAuthenticator.BiometricIdentifier biometric, int userId)
+ throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver
+ .onAuthenticationSucceeded(deviceId, (Fingerprint)biometric, userId);
}
+ }
- @Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
+ @Override
+ public void onAuthenticationFailed(long deviceId) throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onAuthenticationFailed(deviceId);
}
- };
+ }
- int lockoutMode = getLockoutMode();
- if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
- Slog.v(TAG, "In lockout mode(" + lockoutMode +
- ") ; disallowing authentication");
- int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
- if (!client.onError(errorCode, 0 /* vendorCode */)) {
- Slog.w(TAG, "Cannot send permanent lockout message to client");
+ @Override
+ public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onError(deviceId, error, vendorCode);
}
- return;
}
- startClient(client, true /* initiatedByClient */);
- }
-
- private void startEnrollment(IBinder token, byte [] cryptoToken, int userId,
- IFingerprintServiceReceiver receiver, int flags, boolean restricted,
- String opPackageName) {
- updateActiveGroup(userId, opPackageName);
-
- final int groupId = userId; // default group for fingerprint enrollment
- EnrollClient client = new EnrollClient(getContext(), mHalDeviceId, token, receiver,
- userId, groupId, cryptoToken, restricted, opPackageName) {
-
- @Override
- public IBiometricsFingerprint getFingerprintDaemon() {
- return FingerprintService.this.getFingerprintDaemon();
+ @Override
+ public void onRemoved(long deviceId, int fingerId, int groupId, int remaining)
+ throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onRemoved(deviceId, fingerId, groupId, remaining);
}
+ }
- @Override
- public void notifyUserActivity() {
- FingerprintService.this.userActivity();
+ @Override
+ public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining)
+ throws RemoteException {
+ if (mFingerprintServiceReceiver != null) {
+ mFingerprintServiceReceiver.onEnumerated(deviceId, fingerId, groupId, remaining);
}
- };
- startClient(client, true /* initiatedByClient */);
- }
-
- // attempt counter should only be cleared when Keyguard goes away or when
- // a fingerprint is successfully authenticated
- protected void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
- if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
- Slog.v(TAG, "Reset fingerprint 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 class FingerprintServiceLockoutResetMonitor implements IBinder.DeathRecipient {
+ private final class FingerprintServiceLockoutResetMonitor implements IBinder.DeathRecipient {
private static final long WAKELOCK_TIMEOUT_MS = 2000;
private final IFingerprintServiceLockoutResetCallback mCallback;
}
}
+ /**
+ * An internal class to help clean up unknown fingerprints in the hardware and software.
+ */
+ private final class InternalEnumerateClient extends BiometricService.EnumerateClientImpl {
+
+ private List<Fingerprint> mEnrolledList;
+ private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
+
+ public InternalEnumerateClient(Context context, DaemonWrapper daemon, long halDeviceId,
+ IBinder token, ServiceListener listener, int groupId, int userId,
+ boolean restricted, String owner, List<Fingerprint> enrolledList) {
+ super(context, daemon, halDeviceId, token, listener, groupId, userId, restricted,
+ owner);
+ mEnrolledList = enrolledList;
+ }
+
+ private void handleEnumeratedFingerprint(int fingerId, int groupId, int remaining) {
+ boolean matched = false;
+ for (int i = 0; i < mEnrolledList.size(); i++) {
+ if (mEnrolledList.get(i).getFingerId() == fingerId) {
+ mEnrolledList.remove(i);
+ matched = true;
+ break;
+ }
+ }
+
+ // fingerId 0 means no fingerprints are in hardware
+ if (!matched && fingerId != 0) {
+ Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
+ mUnknownFingerprints.add(fingerprint);
+ }
+ }
+
+ private void doFingerprintCleanup() {
+ if (mEnrolledList == null) {
+ return;
+ }
+
+ for (Fingerprint f : mEnrolledList) {
+ Slog.e(TAG, "doFingerprintCleanup(): Removing dangling enrolled fingerprint: "
+ + f.getName() + " " + f.getFingerId() + " " + f.getGroupId()
+ + " " + f.getDeviceId());
+ FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(),
+ f.getFingerId(), getTargetUserId());
+ }
+ mEnrolledList.clear();
+ }
+
+ public List<Fingerprint> getUnknownFingerprints() {
+ return mUnknownFingerprints;
+ }
+
+ @Override
+ public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
+ handleEnumeratedFingerprint(fingerId, groupId, remaining);
+ if (remaining == 0) {
+ doFingerprintCleanup();
+ }
+ return remaining == 0;
+ }
+ }
+
+ /**
+ * An internal class to help clean up unknown fingerprints in hardware and software.
+ */
+ private final class InternalRemovalClient extends BiometricService.RemovalClientImpl {
+ public InternalRemovalClient(Context context,
+ DaemonWrapper daemon, long halDeviceId, IBinder token,
+ ServiceListener listener, int fingerId, int groupId, int userId, boolean restricted,
+ String owner) {
+ super(context, daemon, halDeviceId, token, listener, fingerId, groupId, userId,
+ restricted,
+ owner);
+ }
+ }
+
+ private final FingerprintMetrics mFingerprintMetrics = new FingerprintMetrics();
+ private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors =
+ new ArrayList<>();
+ private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
+ new CopyOnWriteArrayList<>();
+ private final Map<Integer, Long> mAuthenticatorIds =
+ Collections.synchronizedMap(new HashMap<>());
+ private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
+ private final PowerManager mPowerManager;
+
+ @GuardedBy("this")
+ private IBiometricsFingerprint mDaemon;
+
+ private Context mContext;
+ private long mHalDeviceId;
+ private IStatusBarService mStatusBarService;
+ private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
+ private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
+
+ /**
+ * Receives callbacks from the HAL.
+ */
private IBiometricsFingerprintClientCallback mDaemonCallback =
new IBiometricsFingerprintClientCallback.Stub() {
-
@Override
public void onEnrollResult(final long deviceId, final int fingerId, final int groupId,
final int remaining) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- handleEnrollResult(deviceId, fingerId, groupId, remaining);
- }
+ mHandler.post(() -> {
+ FingerprintService.super.handleEnrollResult(deviceId, fingerId, groupId, remaining);
});
}
@Override
public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- handleAcquired(deviceId, acquiredInfo, vendorCode);
- }
+ mHandler.post(() -> {
+ FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode);
});
}
@Override
public void onAuthenticated(final long deviceId, final int fingerId, final int groupId,
ArrayList<Byte> token) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- handleAuthenticated(deviceId, fingerId, groupId, token);
- }
+ mHandler.post(() -> {
+ FingerprintService.super.handleAuthenticated(deviceId, fingerId, groupId, token);
});
}
@Override
public void onError(final long deviceId, final int error, final int vendorCode) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- handleError(deviceId, error, vendorCode);
+ mHandler.post(() -> {
+ ClientMonitor client = getCurrentClient();
+ if (client instanceof InternalRemovalClient
+ || client instanceof InternalEnumerateClient) {
+ clearEnumerateState();
+ }
+ FingerprintService.super.handleError(deviceId, error, vendorCode);
+
+ // TODO: this chunk of code should be common to all biometric services
+ if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+ // If we get HW_UNAVAILABLE, try to connect again later...
+ Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
+ synchronized (this) {
+ mDaemon = null;
+ mHalDeviceId = 0;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
}
});
}
@Override
- public void onRemoved(final long deviceId, final int fingerId, final int groupId, final int remaining) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- handleRemoved(deviceId, fingerId, groupId, remaining);
+ public void onRemoved(final long deviceId, final int fingerId, final int groupId,
+ final int remaining) {
+ mHandler.post(() -> {
+ ClientMonitor client = getCurrentClient();
+ FingerprintService.super.handleRemoved(deviceId, fingerId, groupId, remaining);
+ if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
+ cleanupUnknownFingerprints();
+ } else if (client instanceof InternalRemovalClient){
+ clearEnumerateState();
}
});
}
@Override
public void onEnumerate(final long deviceId, final int fingerId, final int groupId,
final int remaining) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- handleEnumerate(deviceId, fingerId, groupId, remaining);
- }
+ mHandler.post(() -> {
+ // TODO: factor out common enumerate logic if possible
+ FingerprintService.this.handleEnumerate(deviceId, fingerId, groupId, remaining);
});
+
}
};
- private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
- @Override // Binder call
- public long preEnroll(IBinder token) {
- checkPermission(MANAGE_FINGERPRINT);
- return startPreEnroll(token);
+ /**
+ * Wraps the HAL-specific code and is passed to the ClientMonitor implementations so that they
+ * can be shared between the multiple biometric services.
+ */
+ private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() {
+ protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. see errno.h.
+ @Override
+ public int authenticate(long operationId, int groupId) throws RemoteException {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "authenticate(): no fingerprint HAL!");
+ return ERROR_ESRCH;
+ }
+ return daemon.authenticate(operationId, groupId);
}
- @Override // Binder call
- public int postEnroll(IBinder token) {
- checkPermission(MANAGE_FINGERPRINT);
- return startPostEnroll(token);
+ @Override
+ public int cancel() throws RemoteException {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "cancel(): no fingerprint HAL!");
+ return ERROR_ESRCH;
+ }
+ return daemon.cancel();
}
- @Override // Binder call
- public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
- final IFingerprintServiceReceiver receiver, final int flags,
- final String opPackageName) {
- checkPermission(MANAGE_FINGERPRINT);
- final int limit = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
-
- final int enrolled = FingerprintService.this.getEnrolledFingerprints(userId).size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many fingerprints registered");
- return;
+ @Override
+ public int remove(int groupId, int biometricId) throws RemoteException {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "remove(): no fingerprint HAL!");
+ return ERROR_ESRCH;
}
+ return daemon.remove(groupId, biometricId);
+ }
- // Group ID is arbitrarily set to parent profile user ID. It just represents
- // the default fingerprints for the user.
- if (!isCurrentUserOrProfile(userId)) {
- return;
+ @Override
+ public int enumerate() throws RemoteException {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "enumerate(): no fingerprint HAL!");
+ return ERROR_ESRCH;
}
+ return daemon.enumerate();
+ }
- final boolean restricted = isRestricted();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- startEnrollment(token, cryptoToken, userId, receiver, flags,
- restricted, opPackageName);
- }
- });
+ @Override
+ public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException {
+ Slog.v(TAG, "startEnroll()");
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "enroll(): no fingerprint HAL!");
+ return ERROR_ESRCH;
+ }
+ return daemon.enroll(cryptoToken, groupId, timeout);
}
+ };
- private boolean isRestricted() {
- // Only give privileged apps (like Settings) access to fingerprint info
- final boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
- return restricted;
+ public FingerprintService(Context context) {
+ super(context);
+ mContext = context;
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+ // TODO: can this be retrieved from AuthenticationClient, or BiometricService?
+ mStatusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
+ SystemServerInitThreadPool.get().submit(this::getFingerprintDaemon, TAG + ".onStart");
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getFailedAttemptsLockoutTimed() {
+ return MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED;
+ }
+
+ @Override
+ protected int getFailedAttemptsLockoutPermanent() {
+ return MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT;
+ }
+
+ @Override
+ protected Metrics getMetrics() {
+ return mFingerprintMetrics;
+ }
+
+ @Override
+ protected boolean hasReachedEnrollmentLimit(int userId) {
+ final int limit = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
+ final int enrolled = FingerprintService.this.getEnrolledFingerprints(userId).size();
+ if (enrolled >= limit) {
+ Slog.w(TAG, "Too many fingerprints registered");
+ return true;
}
+ return false;
+ }
- @Override // Binder call
- public void cancelEnrollment(final IBinder token) {
- checkPermission(MANAGE_FINGERPRINT);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- ClientMonitor client = mCurrentClient;
- if (client instanceof EnrollClient && client.getToken() == token) {
- client.stop(client.getToken() == token);
+ @Override
+ protected void updateActiveGroup(int userId, String clientPackage) {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+
+ if (daemon != null) {
+ try {
+ userId = getUserOrWorkProfileId(clientPackage, userId);
+ if (userId != mCurrentUserId) {
+ int firstSdkInt = Build.VERSION.FIRST_SDK_INT;
+ if (firstSdkInt < Build.VERSION_CODES.BASE) {
+ Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " +
+ "at least VERSION_CODES.BASE");
+ }
+ File baseDir;
+ if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
+ baseDir = Environment.getUserSystemDirectory(userId);
+ } else {
+ baseDir = Environment.getDataVendorDeDirectory(userId);
}
+
+ File fpDir = new File(baseDir, FP_DATA_DIR);
+ if (!fpDir.exists()) {
+ if (!fpDir.mkdir()) {
+ Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
+ return;
+ }
+ // Calling mkdir() from this process will create a directory with our
+ // permissions (inherited from the containing dir). This command fixes
+ // the label.
+ if (!SELinux.restorecon(fpDir)) {
+ Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
+ return;
+ }
+ }
+
+ daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
+ mCurrentUserId = userId;
}
- });
+ mAuthenticatorIds.put(userId,
+ hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId() : 0L);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setActiveGroup():", e);
+ }
}
+ }
- @Override // Binder call
- public void authenticate(final IBinder token, final long opId, final int groupId,
- final IFingerprintServiceReceiver receiver, final int flags,
- final String opPackageName, final Bundle bundle,
- final IBiometricPromptReceiver dialogReceiver) {
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int callingUserId = UserHandle.getCallingUserId();
- final boolean restricted = isRestricted();
+ @Override
+ protected String getLockoutResetIntent() {
+ return ACTION_LOCKOUT_RESET;
+ }
- if (!canUseFingerprint(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
- callingUserId)) {
- if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName);
- return;
- }
+ @Override
+ protected String getLockoutBroadcastPermission() {
+ return RESET_FINGERPRINT_LOCKOUT;
+ }
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- MetricsLogger.histogram(mContext, "fingerprint_token", 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;
+ @Override
+ protected long getHalDeviceId() {
+ return mHalDeviceId;
+ }
- startAuthentication(token, opId, callingUserId, groupId, receiver,
- flags, restricted, opPackageName, bundle, dialogReceiver);
- }
- });
+ @Override
+ protected void handleUserSwitching(int userId) {
+ if (getCurrentClient() instanceof InternalRemovalClient
+ || getCurrentClient() instanceof InternalEnumerateClient) {
+ Slog.w(TAG, "User switched while performing cleanup");
+ removeClient(getCurrentClient());
+ clearEnumerateState();
}
+ updateActiveGroup(userId, null);
+ doFingerprintCleanupForUser(userId);
+ }
- @Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName) {
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int callingUserId = UserHandle.getCallingUserId();
- if (!canUseFingerprint(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
- callingUserId)) {
- if (DEBUG) Slog.v(TAG, "cancelAuthentication(): reject " + opPackageName);
- return;
- }
+ @Override
+ protected boolean hasEnrolledBiometrics(int userId) {
+ if (userId != UserHandle.getCallingUserId()) {
+ checkPermission(INTERACT_ACROSS_USERS);
+ }
+ return mFingerprintUtils.getFingerprintsForUser(mContext, userId).size() > 0;
+ }
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- ClientMonitor client = mCurrentClient;
- if (client instanceof AuthenticationClient) {
- if (client.getToken() == token) {
- if (DEBUG) Slog.v(TAG, "stop client " + client.getOwnerString());
- client.stop(client.getToken() == token);
- } else {
- if (DEBUG) Slog.v(TAG, "can't stop client "
- + client.getOwnerString() + " since tokens don't match");
- }
- } else if (client != null) {
- if (DEBUG) Slog.v(TAG, "can't cancel non-authenticating client "
- + client.getOwnerString());
- }
- }
- });
+ @Override
+ protected String getManageBiometricPermission() {
+ return MANAGE_FINGERPRINT;
+ }
+
+ @Override
+ protected void checkUseBiometricPermission() {
+ if (getContext().checkCallingPermission(USE_FINGERPRINT)
+ != PackageManager.PERMISSION_GRANTED) {
+ checkPermission(USE_BIOMETRIC);
}
+ }
- @Override // Binder call
- public void setActiveUser(final int userId) {
- checkPermission(MANAGE_FINGERPRINT);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- updateActiveGroup(userId, null);
- }
- });
+ @Override
+ protected int getAppOp() {
+ return AppOpsManager.OP_USE_FINGERPRINT;
+ }
+
+ @Override
+ protected void notifyLockoutResetMonitors() {
+ for (int i = 0; i < mLockoutMonitors.size(); i++) {
+ mLockoutMonitors.get(i).sendLockoutReset();
}
+ }
- @Override // Binder call
- public void remove(final IBinder token, final int fingerId, final int groupId,
- final int userId, final IFingerprintServiceReceiver receiver) {
- checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
- final boolean restricted = isRestricted();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- startRemove(token, fingerId, groupId, userId, receiver,
- restricted, false /* internal */);
- }
- });
+ @Override
+ protected void notifyClientActiveCallbacks(boolean isActive) {
+ List<IFingerprintClientActiveCallback> callbacks = mClientActiveCallbacks;
+ for (int i = 0; i < callbacks.size(); i++) {
+ try {
+ callbacks.get(i).onClientActiveChanged(isActive);
+ } catch (RemoteException re) {
+ // If the remote is dead, stop notifying it
+ mClientActiveCallbacks.remove(callbacks.get(i));
+ }
}
+ }
- @Override // Binder call
- public void enumerate(final IBinder token, final int userId,
- final IFingerprintServiceReceiver receiver) {
- checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
- final boolean restricted = isRestricted();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- startEnumerate(token, userId, receiver, restricted, false /* internal */);
- }
- });
+ /** Gets the fingerprint daemon */
+ private synchronized IBiometricsFingerprint getFingerprintDaemon() {
+ if (mDaemon == null) {
+ Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
+ try {
+ mDaemon = IBiometricsFingerprint.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened. Logged below.
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get biometric interface", e);
+ }
+ if (mDaemon == null) {
+ Slog.w(TAG, "fingerprint HIDL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0);
+
+ try {
+ mHalDeviceId = mDaemon.setNotify(mDaemonCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to open fingerprint HAL", e);
+ mDaemon = null; // try again later!
+ }
+
+ if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId);
+ if (mHalDeviceId != 0) {
+ loadAuthenticatorIds();
+ updateActiveGroup(ActivityManager.getCurrentUser(), null);
+ doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
+ } else {
+ Slog.w(TAG, "Failed to open Fingerprint HAL!");
+ MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
+ mDaemon = null;
+ }
}
+ return mDaemon;
+ }
- @Override // Binder call
- public boolean isHardwareDetected(long deviceId, String opPackageName) {
- if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
- UserHandle.getCallingUserId())) {
- return false;
+ /** Populates existing authenticator ids. To be used only during the start of the service. */
+ private 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(mContext).getUsers(true /* excludeDying */)) {
+ int userId = getUserOrWorkProfileId(null, user.id);
+ if (!mAuthenticatorIds.containsKey(userId)) {
+ updateActiveGroup(userId, null);
}
+ }
- final long token = Binder.clearCallingIdentity();
- try {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- return daemon != null && mHalDeviceId != 0;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ t = System.currentTimeMillis() - t;
+ if (t > 1000) {
+ Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
}
+ }
- @Override // Binder call
- public void rename(final int fingerId, final int groupId, final String name) {
- checkPermission(MANAGE_FINGERPRINT);
- if (!isCurrentUserOrProfile(groupId)) {
- return;
- }
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mFingerprintUtils.renameFingerprintForUser(mContext, fingerId,
- groupId, name);
- }
- });
+ /**
+ * This method should be called upon connection to the daemon, and when user switches.
+ * @param userId
+ */
+ private void doFingerprintCleanupForUser(int userId) {
+ if (CLEANUP_UNUSED_FP) {
+ enumerateUser(userId);
}
+ }
- @Override // Binder call
- public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) {
- if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
- UserHandle.getCallingUserId())) {
- return Collections.emptyList();
- }
+ private void clearEnumerateState() {
+ if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
+ mUnknownFingerprints.clear();
+ }
- return FingerprintService.this.getEnrolledFingerprints(userId);
- }
+ private void enumerateUser(int userId) {
+ if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
- @Override // Binder call
- public boolean hasEnrolledFingerprints(int userId, String opPackageName) {
- if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
- UserHandle.getCallingUserId())) {
- return false;
- }
+ final boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+ final List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
+
+ InternalEnumerateClient client = new InternalEnumerateClient(getContext(), mDaemonWrapper,
+ mHalDeviceId, mToken, new ServiceListenerImpl(null), userId, userId, restricted,
+ getContext().getOpPackageName(), enrolledList);
+ enumerateInternal(client);
+ }
- return FingerprintService.this.hasEnrolledFingerprints(userId);
+ // Remove unknown fingerprints from hardware
+ private void cleanupUnknownFingerprints() {
+ if (!mUnknownFingerprints.isEmpty()) {
+ UserFingerprint uf = mUnknownFingerprints.get(0);
+ mUnknownFingerprints.remove(uf);
+ boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+ InternalRemovalClient client = new InternalRemovalClient(getContext(), mDaemonWrapper,
+ mHalDeviceId, mToken, new ServiceListenerImpl(null), uf.f.getFingerId(),
+ uf.f.getGroupId(), uf.userId, restricted, getContext().getOpPackageName());
+ removeInternal(client);
+ } else {
+ clearEnumerateState();
}
+ }
- @Override // Binder call
- public long getAuthenticatorId(String opPackageName) {
- // In this method, we're not checking whether the caller is permitted to use fingerprint
- // API because current authenticator ID is leaked (in a more contrived way) via Android
- // Keystore (android.security.keystore package): the user of that API can create a key
- // which requires fingerprint authentication for its use, and then query the key's
- // characteristics (hidden API) which returns, among other things, fingerprint
- // authenticator ID which was active at key creation time.
- //
- // Reason: The part of Android Keystore which runs inside an app's process invokes this
- // method in certain cases. Those cases are not always where the developer demonstrates
- // explicit intent to use fingerprint functionality. Thus, to avoiding throwing an
- // unexpected SecurityException this method does not check whether its caller is
- // permitted to use fingerprint API.
- //
- // The permission check should be restored once Android Keystore no longer invokes this
- // method from inside app processes.
+ private void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
+ ClientMonitor client = getCurrentClient();
- return FingerprintService.this.getAuthenticatorId(opPackageName);
+ if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
+ return;
}
+ client.onEnumerationResult(fingerId, groupId, remaining);
- @Override // Binder call
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ // All fingerprints in hardware for this user were enumerated
+ if (remaining == 0) {
+ if (client instanceof InternalEnumerateClient) {
+ List<Fingerprint> unknownFingerprints =
+ ((InternalEnumerateClient) client).getUnknownFingerprints();
- final long ident = Binder.clearCallingIdentity();
- try {
- if (args.length > 0 && "--proto".equals(args[0])) {
- dumpProto(fd);
- } else {
- dumpInternal(pw);
+ if (!unknownFingerprints.isEmpty()) {
+ Slog.w(TAG, "Adding " + unknownFingerprints.size() +
+ " fingerprints for deletion");
}
- } finally {
- Binder.restoreCallingIdentity(ident);
+ for (Fingerprint f : unknownFingerprints) {
+ mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
+ }
+ removeClient(client);
+ cleanupUnknownFingerprints();
+ } else {
+ removeClient(client);
}
}
+ }
- @Override // Binder call
- public void resetTimeout(byte [] token) {
- checkPermission(RESET_FINGERPRINT_LOCKOUT);
- // TODO: confirm security token when we move timeout management into the HAL layer.
- mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
+ private long startPreEnroll(IBinder token) {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "startPreEnroll: no fingerprint HAL!");
+ return 0;
}
-
- @Override
- public void addLockoutResetCallback(final IFingerprintServiceLockoutResetCallback callback)
- throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- addLockoutResetMonitor(
- new FingerprintServiceLockoutResetMonitor(callback));
- }
- });
+ try {
+ return daemon.preEnroll();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "startPreEnroll failed", e);
}
+ return 0;
+ }
- @Override
- public boolean isClientActive() {
- checkPermission(MANAGE_FINGERPRINT);
- synchronized(FingerprintService.this) {
- return (mCurrentClient != null) || (mPendingClient != null);
- }
+ private int startPostEnroll(IBinder token) {
+ IBiometricsFingerprint daemon = getFingerprintDaemon();
+ if (daemon == null) {
+ Slog.w(TAG, "startPostEnroll: no fingerprint HAL!");
+ return 0;
}
-
- @Override
- public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
- checkPermission(MANAGE_FINGERPRINT);
- mClientActiveCallbacks.add(callback);
+ try {
+ return daemon.postEnroll();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "startPostEnroll failed", e);
}
+ return 0;
+ }
- @Override
- public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
- checkPermission(MANAGE_FINGERPRINT);
- mClientActiveCallbacks.remove(callback);
+ private List<Fingerprint> getEnrolledFingerprints(int userId) {
+ return mFingerprintUtils.getFingerprintsForUser(mContext, userId);
+ }
+
+ private void addLockoutResetMonitor(FingerprintServiceLockoutResetMonitor monitor) {
+ if (!mLockoutMonitors.contains(monitor)) {
+ mLockoutMonitors.add(monitor);
}
}
+ private void removeLockoutResetCallback(
+ FingerprintServiceLockoutResetMonitor monitor) {
+ mLockoutMonitors.remove(monitor);
+ }
+
+ /***
+ * @param opPackageName the name of the calling package
+ * @return authenticator id for the calling user
+ */
+ private long getAuthenticatorId(String opPackageName) {
+ final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
+ return mAuthenticatorIds.getOrDefault(userId, 0L);
+ }
+
private void dumpInternal(PrintWriter pw) {
JSONObject dump = new JSONObject();
try {
mPerformanceMap.clear();
mCryptoPerformanceMap.clear();
}
-
- @Override
- public void onStart() {
- publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
- SystemServerInitThreadPool.get().submit(this::getFingerprintDaemon, TAG + ".onStart");
- listenForUserSwitches();
- }
-
- private void updateActiveGroup(int userId, String clientPackage) {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
-
- if (daemon != null) {
- try {
- userId = getUserOrWorkProfileId(clientPackage, userId);
- if (userId != mCurrentUserId) {
- int firstSdkInt = Build.VERSION.FIRST_SDK_INT;
- if (firstSdkInt < Build.VERSION_CODES.BASE) {
- Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " +
- "at least VERSION_CODES.BASE");
- }
- File baseDir;
- if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
- baseDir = Environment.getUserSystemDirectory(userId);
- } else {
- baseDir = Environment.getDataVendorDeDirectory(userId);
- }
-
- File fpDir = new File(baseDir, FP_DATA_DIR);
- if (!fpDir.exists()) {
- if (!fpDir.mkdir()) {
- Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
- return;
- }
- // Calling mkdir() from this process will create a directory with our
- // permissions (inherited from the containing dir). This command fixes
- // the label.
- if (!SELinux.restorecon(fpDir)) {
- Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
- return;
- }
- }
-
- daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
- mCurrentUserId = userId;
- }
- mAuthenticatorIds.put(userId,
- hasEnrolledFingerprints(userId) ? daemon.getAuthenticatorId() : 0L);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setActiveGroup():", e);
- }
- }
- }
-
- /**
- * @param clientPackage the package of the caller
- * @return the profile id
- */
- private int getUserOrWorkProfileId(String clientPackage, int userId) {
- if (!isKeyguard(clientPackage) && isWorkProfile(userId)) {
- return userId;
- }
- return getEffectiveUserId(userId);
- }
-
- /**
- * @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 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();
- }
- }, TAG);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to listen for user switching event" ,e);
- }
- }
-
- /***
- * @param opPackageName the name of the calling package
- * @return authenticator id for the calling user
- */
- public long getAuthenticatorId(String opPackageName) {
- final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
- return mAuthenticatorIds.getOrDefault(userId, 0L);
- }
}
+++ /dev/null
-/*
- * Copyright (C) 2017 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.fingerprint;
-
-import android.content.Context;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.IBinder;
-import android.util.Slog;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An internal class to help clean up unknown fingerprints in the hardware and software
- */
-public abstract class InternalEnumerateClient extends EnumerateClient {
-
- private List<Fingerprint> mEnrolledList;
- private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
-
- public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int groupId, int userId,
- boolean restricted, String owner, List<Fingerprint> enrolledList) {
-
- super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
- mEnrolledList = enrolledList;
- }
-
- private void handleEnumeratedFingerprint(int fingerId, int groupId, int remaining) {
-
- boolean matched = false;
- for (int i=0; i<mEnrolledList.size(); i++) {
- if (mEnrolledList.get(i).getFingerId() == fingerId) {
- mEnrolledList.remove(i);
- matched = true;
- break;
- }
- }
-
- // fingerId 0 means no fingerprints are in hardware
- if (!matched && fingerId != 0) {
- Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
- mUnknownFingerprints.add(fingerprint);
- }
- }
-
- private void doFingerprintCleanup() {
-
- if (mEnrolledList == null) {
- return;
- }
-
- for (Fingerprint f : mEnrolledList) {
- Slog.e(TAG, "Internal Enumerate: Removing dangling enrolled fingerprint: "
- + f.getName() + " " + f.getFingerId() + " " + f.getGroupId()
- + " " + f.getDeviceId());
-
- FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(),
- f.getFingerId(), getTargetUserId());
- }
- mEnrolledList.clear();
- }
-
- public List<Fingerprint> getUnknownFingerprints() {
- return mUnknownFingerprints;
- }
-
- @Override
- public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
-
- handleEnumeratedFingerprint(fingerId, groupId, remaining);
- if (remaining == 0) {
- doFingerprintCleanup();
- }
-
- return remaining == 0;
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2017 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.fingerprint;
-
-import android.content.Context;
-import android.os.IBinder;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import com.android.server.biometrics.fingerprint.RemovalClient;
-
-public abstract class InternalRemovalClient extends RemovalClient {
-
- public InternalRemovalClient(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int fingerId, int groupId, int userId,
- boolean restricted, String owner) {
-
- super(context, halDeviceId, token, receiver, fingerId, groupId, userId, restricted, owner);
-
- }
-}