/** * Copyright (C) 2014 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.fingerprint; import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManagerNative; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.app.SynchronousUserSwitchObserver; import android.content.ComponentName; import android.content.BroadcastReceiver; 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.fingerprint.IFingerprintServiceLockoutResetCallback; import android.os.Binder; import android.os.DeadObjectException; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; 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.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.server.SystemService; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintService; import android.hardware.fingerprint.IFingerprintDaemon; import android.hardware.fingerprint.IFingerprintDaemonCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.Manifest.permission.MANAGE_FINGERPRINT; import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT; import static android.Manifest.permission.USE_FINGERPRINT; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * A service to manage multiple clients that want to access the fingerprint HAL API. * The service is responsible for maintaining a list of clients and dispatching all * fingerprint -related events. * * @hide */ public class FingerprintService extends SystemService implements IBinder.DeathRecipient { static final String TAG = "FingerprintService"; static final boolean DEBUG = true; private static final String FP_DATA_DIR = "fpdata"; private static final String FINGERPRINTD = "android.hardware.fingerprint.IFingerprintDaemon"; private static final int MSG_USER_SWITCHING = 10; private static final String ACTION_LOCKOUT_RESET = "com.android.server.fingerprint.ACTION_LOCKOUT_RESET"; private final ArrayList mLockoutMonitors = new ArrayList<>(); private final AppOpsManager mAppOps; private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000; private static final int MAX_FAILED_ATTEMPTS = 5; 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_CURRENT; private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance(); private Context mContext; private long mHalDeviceId; private int mFailedAttempts; private IFingerprintDaemon mDaemon; private final PowerManager mPowerManager; private final AlarmManager mAlarmManager; private final UserManager mUserManager; private ClientMonitor mCurrentClient; private ClientMonitor mPendingClient; private long mCurrentAuthenticatorId; 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())) { resetFailedAttempts(); } } }; private final Runnable mResetFailedAttemptsRunnable = new Runnable() { @Override public void run() { resetFailedAttempts(); } }; 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); } }; 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); } @Override public void binderDied() { Slog.v(TAG, "fingerprintd died"); mDaemon = null; handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); } public IFingerprintDaemon getFingerprintDaemon() { if (mDaemon == null) { mDaemon = IFingerprintDaemon.Stub.asInterface(ServiceManager.getService(FINGERPRINTD)); if (mDaemon != null) { try { mDaemon.asBinder().linkToDeath(this, 0); mDaemon.init(mDaemonCallback); mHalDeviceId = mDaemon.openHal(); if (mHalDeviceId != 0) { updateActiveGroup(ActivityManager.getCurrentUser(), null); } else { Slog.w(TAG, "Failed to open Fingerprint HAL!"); mDaemon = null; } } catch (RemoteException e) { Slog.e(TAG, "Failed to open fingeprintd HAL", e); mDaemon = null; // try again later! } } else { Slog.w(TAG, "fingerprint service not available"); } } return mDaemon; } protected void handleEnumerate(long deviceId, int[] fingerIds, int[] groupIds) { if (fingerIds.length != groupIds.length) { Slog.w(TAG, "fingerIds and groupIds differ in length: f[]=" + Arrays.toString(fingerIds) + ", g[]=" + Arrays.toString(groupIds)); return; } if (DEBUG) Slog.w(TAG, "Enumerate: f[]=" + fingerIds + ", g[]=" + groupIds); // TODO: update fingerprint/name pairs } protected void handleError(long deviceId, int error) { ClientMonitor client = mCurrentClient; if (client != null && client.onError(error)) { removeClient(client); } 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; } } } protected void handleRemoved(long deviceId, int fingerId, int groupId) { ClientMonitor client = mCurrentClient; if (client != null && client.onRemoved(fingerId, groupId)) { removeClient(client); } } protected void handleAuthenticated(long deviceId, int fingerId, int groupId) { ClientMonitor client = mCurrentClient; if (client != null && client.onAuthenticated(fingerId, groupId)) { removeClient(client); } } protected void handleAcquired(long deviceId, int acquiredInfo) { ClientMonitor client = mCurrentClient; if (client != null && client.onAcquired(acquiredInfo)) { removeClient(client); } } protected void handleEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { ClientMonitor client = mCurrentClient; if (client != null && client.onEnrollResult(fingerId, groupId, remaining)) { removeClient(client); } } private void userActivity() { long now = SystemClock.uptimeMillis(); mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); } void handleUserSwitching(int userId) { updateActiveGroup(userId, null); } 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; } } private boolean inLockoutMode() { return mFailedAttempts >= MAX_FAILED_ATTEMPTS; } private void scheduleLockoutReset() { mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent()); } private void cancelLockoutReset() { mAlarmManager.cancel(getLockoutResetIntent()); } private PendingIntent getLockoutResetIntent() { return PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT); } public long startPreEnroll(IBinder token) { IFingerprintDaemon daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "startPreEnroll: no fingeprintd!"); return 0; } try { return daemon.preEnroll(); } catch (RemoteException e) { Slog.e(TAG, "startPreEnroll failed", e); } return 0; } public int startPostEnroll(IBinder token) { IFingerprintDaemon daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "startPostEnroll: no fingeprintd!"); return 0; } try { return daemon.postEnroll(); } catch (RemoteException e) { Slog.e(TAG, "startPostEnroll failed", e); } return 0; } /** * Calls fingerprintd 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()); 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 + ")"); newClient.start(); } } void startRemove(IBinder token, int fingerId, int groupId, int userId, IFingerprintServiceReceiver receiver, boolean restricted) { IFingerprintDaemon daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "startRemove: no fingeprintd!"); return; } RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token, receiver, fingerId, groupId, userId, restricted, token.toString()) { @Override public void notifyUserActivity() { FingerprintService.this.userActivity(); } @Override public IFingerprintDaemon getFingerprintDaemon() { return FingerprintService.this.getFingerprintDaemon(); } }; startClient(client, true); } public List 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); // Allow current user or profiles of the current user... for (int profileId : um.getEnabledProfileIds(userId)) { if (profileId == userId) { return true; } } return false; } private boolean isForegroundActivity(int uid, int pid) { try { List procs = ActivityManagerNative.getDefault().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) { return true; } } } catch (RemoteException e) { Slog.w(TAG, "am.getRunningAppProcesses() failed"); } return false; } /** * @param opPackageName name of package for caller * @param foregroundOnly only allow this call while app is in the foreground * @return true if caller can use fingerprint API */ private boolean canUseFingerprint(String opPackageName, boolean foregroundOnly, int uid, int pid) { checkPermission(USE_FINGERPRINT); if (isKeyguard(opPackageName)) { return true; // Keyguard is always allowed } if (!isCurrentUserOrProfile(UserHandle.getCallingUserId())) { 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 (foregroundOnly && !isForegroundActivity(uid, pid)) { Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground"); return false; } return true; } /** * @param clientPackage * @return true if this is keyguard package */ private boolean isKeyguard(String clientPackage) { return mKeyguardPackage.equals(clientPackage); } private void addLockoutResetMonitor(FingerprintServiceLockoutResetMonitor monitor) { if (!mLockoutMonitors.contains(monitor)) { mLockoutMonitors.add(monitor); } } private void removeLockoutResetCallback( FingerprintServiceLockoutResetMonitor monitor) { mLockoutMonitors.remove(monitor); } private void notifyLockoutResetMonitors() { for (int i = 0; i < mLockoutMonitors.size(); i++) { mLockoutMonitors.get(i).sendLockoutReset(); } } private void startAuthentication(IBinder token, long opId, int callingUserId, int groupId, IFingerprintServiceReceiver receiver, int flags, boolean restricted, String opPackageName) { updateActiveGroup(groupId, opPackageName); if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")"); AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token, receiver, callingUserId, groupId, opId, restricted, opPackageName) { @Override public boolean handleFailedAttempt() { mFailedAttempts++; if (inLockoutMode()) { // Failing multiple times will continue to push out the lockout time. scheduleLockoutReset(); return true; } return false; } @Override public void resetFailedAttempts() { FingerprintService.this.resetFailedAttempts(); } @Override public void notifyUserActivity() { FingerprintService.this.userActivity(); } @Override public IFingerprintDaemon getFingerprintDaemon() { return FingerprintService.this.getFingerprintDaemon(); } }; if (inLockoutMode()) { Slog.v(TAG, "In lockout mode; disallowing authentication"); // Don't bother starting the client. Just send the error message. if (!client.onError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { Slog.w(TAG, "Cannot send timeout message to client"); } 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 IFingerprintDaemon getFingerprintDaemon() { return FingerprintService.this.getFingerprintDaemon(); } @Override public void notifyUserActivity() { FingerprintService.this.userActivity(); } }; startClient(client, true /* initiatedByClient */); } protected void resetFailedAttempts() { if (DEBUG && inLockoutMode()) { Slog.v(TAG, "Reset fingerprint lockout"); } mFailedAttempts = 0; // If we're asked to reset failed attempts externally (i.e. from Keyguard), // the alarm might still be pending; remove it. cancelLockoutReset(); notifyLockoutResetMonitors(); } private class FingerprintServiceLockoutResetMonitor { private final IFingerprintServiceLockoutResetCallback mCallback; public FingerprintServiceLockoutResetMonitor( IFingerprintServiceLockoutResetCallback callback) { mCallback = callback; } public void sendLockoutReset() { if (mCallback != null) { try { mCallback.onLockoutReset(mHalDeviceId); } catch (DeadObjectException e) { Slog.w(TAG, "Death object while invoking onLockoutReset: ", e); mHandler.post(mRemoveCallbackRunnable); } catch (RemoteException e) { Slog.w(TAG, "Failed to invoke onLockoutReset: ", e); } } } private final Runnable mRemoveCallbackRunnable = new Runnable() { @Override public void run() { removeLockoutResetCallback(FingerprintServiceLockoutResetMonitor.this); } }; } private IFingerprintDaemonCallback mDaemonCallback = new IFingerprintDaemonCallback.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); } }); } @Override public void onAcquired(final long deviceId, final int acquiredInfo) { mHandler.post(new Runnable() { @Override public void run() { handleAcquired(deviceId, acquiredInfo); } }); } @Override public void onAuthenticated(final long deviceId, final int fingerId, final int groupId) { mHandler.post(new Runnable() { @Override public void run() { handleAuthenticated(deviceId, fingerId, groupId); } }); } @Override public void onError(final long deviceId, final int error) { mHandler.post(new Runnable() { @Override public void run() { handleError(deviceId, error); } }); } @Override public void onRemoved(final long deviceId, final int fingerId, final int groupId) { mHandler.post(new Runnable() { @Override public void run() { handleRemoved(deviceId, fingerId, groupId); } }); } @Override public void onEnumerate(final long deviceId, final int[] fingerIds, final int[] groupIds) { mHandler.post(new Runnable() { @Override public void run() { handleEnumerate(deviceId, fingerIds, groupIds); } }); } }; private final class FingerprintServiceWrapper extends IFingerprintService.Stub { @Override // Binder call public long preEnroll(IBinder token) { checkPermission(MANAGE_FINGERPRINT); return startPreEnroll(token); } @Override // Binder call public int postEnroll(IBinder token) { checkPermission(MANAGE_FINGERPRINT); return startPostEnroll(token); } @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; } // Group ID is arbitrarily set to parent profile user ID. It just represents // the default fingerprints for the user. if (!isCurrentUserOrProfile(userId)) { return; } final boolean restricted = isRestricted(); mHandler.post(new Runnable() { @Override public void run() { startEnrollment(token, cryptoToken, userId, receiver, flags, restricted, opPackageName); } }); } private boolean isRestricted() { // Only give privileged apps (like Settings) access to fingerprint info final boolean restricted = !hasPermission(MANAGE_FINGERPRINT); return restricted; } @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 // Binder call public void authenticate(final IBinder token, final long opId, final int groupId, final IFingerprintServiceReceiver receiver, final int flags, final String opPackageName) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getCallingUserId(); final int pid = Binder.getCallingPid(); final boolean restricted = isRestricted(); mHandler.post(new Runnable() { @Override public void run() { MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0); if (!canUseFingerprint(opPackageName, true /* foregroundOnly */, callingUid, pid)) { if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName); return; } startAuthentication(token, opId, callingUserId, groupId, receiver, flags, restricted, opPackageName); } }); } @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName) { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); mHandler.post(new Runnable() { @Override public void run() { if (!canUseFingerprint(opPackageName, true /* foregroundOnly */, uid, pid)) { if (DEBUG) Slog.v(TAG, "cancelAuthentication(): reject " + opPackageName); } else { 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 // Binder call public void setActiveUser(final int userId) { checkPermission(MANAGE_FINGERPRINT); mHandler.post(new Runnable() { @Override public void run() { updateActiveGroup(userId, null); } }); } @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); } }); } @Override // Binder call public boolean isHardwareDetected(long deviceId, String opPackageName) { if (!canUseFingerprint(opPackageName, false /* foregroundOnly */, Binder.getCallingUid(), Binder.getCallingPid())) { return false; } return mHalDeviceId != 0; } @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); } }); } @Override // Binder call public List getEnrolledFingerprints(int userId, String opPackageName) { if (!canUseFingerprint(opPackageName, false /* foregroundOnly */, Binder.getCallingUid(), Binder.getCallingPid())) { return Collections.emptyList(); } if (!isCurrentUserOrProfile(userId)) { return Collections.emptyList(); } return FingerprintService.this.getEnrolledFingerprints(userId); } @Override // Binder call public boolean hasEnrolledFingerprints(int userId, String opPackageName) { if (!canUseFingerprint(opPackageName, false /* foregroundOnly */, Binder.getCallingUid(), Binder.getCallingPid())) { return false; } if (!isCurrentUserOrProfile(userId)) { return false; } return FingerprintService.this.hasEnrolledFingerprints(userId); } @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. return FingerprintService.this.getAuthenticatorId(opPackageName); } @Override // Binder call protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump Fingerprint from from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } final long ident = Binder.clearCallingIdentity(); try { dumpInternal(pw); } finally { Binder.restoreCallingIdentity(ident); } } @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(mResetFailedAttemptsRunnable); } @Override public void addLockoutResetCallback(final IFingerprintServiceLockoutResetCallback callback) throws RemoteException { mHandler.post(new Runnable() { @Override public void run() { addLockoutResetMonitor( new FingerprintServiceLockoutResetMonitor(callback)); } }); } } private void dumpInternal(PrintWriter pw) { JSONObject dump = new JSONObject(); try { dump.put("service", "Fingerprint Manager"); JSONArray sets = new JSONArray(); for (UserInfo user : UserManager.get(getContext()).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); final int N = mFingerprintUtils.getFingerprintsForUser(mContext, userId).size(); JSONObject set = new JSONObject(); set.put("id", userId); set.put("count", N); sets.put(set); } dump.put("prints", sets); } catch (JSONException e) { Slog.e(TAG, "dump formatting failure", e); } pw.println(dump); } @Override public void onStart() { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); IFingerprintDaemon daemon = getFingerprintDaemon(); if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId); listenForUserSwitches(); } private void updateActiveGroup(int userId, String clientPackage) { IFingerprintDaemon daemon = getFingerprintDaemon(); if (daemon != null) { try { userId = getUserOrWorkProfileId(clientPackage, userId); if (userId != mCurrentUserId) { final File systemDir = Environment.getUserSystemDirectory(userId); final File fpDir = new File(systemDir, 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().getBytes()); mCurrentUserId = userId; } mCurrentAuthenticatorId = daemon.getAuthenticatorId(); } 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 info = mUserManager.getUserInfo(userId); return info != null && info.isManagedProfile(); } private void listenForUserSwitches() { try { ActivityManagerNative.getDefault().registerUserSwitchObserver( new SynchronousUserSwitchObserver() { @Override public void onUserSwitching(int newUserId) throws RemoteException { mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */) .sendToTarget(); } @Override public void onUserSwitchComplete(int newUserId) throws RemoteException { // Ignore. } @Override public void onForegroundProfileSwitch(int newProfileId) { // Ignore. } }); } 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 current user */ public long getAuthenticatorId(String opPackageName) { return mCurrentAuthenticatorId; } }