diff options
| author | 2016-04-13 20:28:18 -0700 | |
|---|---|---|
| committer | 2016-04-14 17:53:45 -0700 | |
| commit | cb2ce6f1f0deef80943ece093ae40bacc1f57c44 (patch) | |
| tree | 70303f69f9e24e110d5c8ee06e2082e700a7080c | |
| parent | 4f9b759d8f801a590c38b50dd0bc530ee169957f (diff) | |
Fix bug where fingerprint events can be delivered to the wrong client
- Make FingerprintService more closely track the expected state of fingerprintd.
- Don't switch to a new operation until fingerprintd completes previous operation.
- Refactor clients into separate classes and add tracking logic.
- Add missing enumerate()/cancelEnumeration() methods to IFingerprintDaemon
- Make late-binding decision of "foregroundness" of activity so that it's
decided in the order the events are actually handled.
- Add more logging so we can determine FingerprintService state when errors occur.
- Cache a copy of authenticator_id from the last time it was set so we don't
interrupt the driver during actual authentication.
- Don't allow clients to access authenticator_id unless they're current.
Fixes: 27902478, 26273819
Change-Id: Ic1f9e30bd89bcdbb8fe7f69e0545e68339317721
9 files changed, 934 insertions, 406 deletions
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 4756b3728009..271ec79074e8 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -517,7 +517,8 @@ public class FingerprintManager { if (mService != null) try { mEnrollmentCallback = callback; - mService.enroll(mToken, token, userId, mServiceReceiver, flags); + mService.enroll(mToken, token, userId, mServiceReceiver, flags, + mContext.getOpPackageName()); } catch (RemoteException e) { Log.w(TAG, "Remote exception in enroll: ", e); if (callback != null) { diff --git a/core/java/android/hardware/fingerprint/IFingerprintDaemon.aidl b/core/java/android/hardware/fingerprint/IFingerprintDaemon.aidl index 9c13523e7b18..f40f8a3fdbf2 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintDaemon.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintDaemon.aidl @@ -35,4 +35,6 @@ interface IFingerprintDaemon { int closeHal(); void init(IFingerprintDaemonCallback callback); int postEnroll(); + int enumerate(); + int cancelEnumeration(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 43d5577f3b71..d7915e3e9622 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -35,7 +35,7 @@ interface IFingerprintService { // Start fingerprint enrollment void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, - int flags); + int flags, String opPackageName); // Cancel enrollment in progress void cancelEnrollment(IBinder token); diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java new file mode 100644 index 000000000000..d2f015fad881 --- /dev/null +++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2016 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 com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.MetricsProto.MetricsEvent; + +import android.content.Context; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintDaemon; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.IBinder; +import android.os.RemoteException; +import android.system.ErrnoException; +import android.util.Slog; + +/** + * A class to keep track of the authentication state for a given client. + */ +public abstract class AuthenticationClient extends ClientMonitor { + private long mOpId; + + public abstract boolean handleFailedAttempt(); + public abstract void resetFailedAttempts(); + + public AuthenticationClient(Context context, long halDeviceId, IBinder token, + IFingerprintServiceReceiver receiver, int userId, int groupId, long opId, + boolean restricted, String owner) { + super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner); + mOpId = opId; + } + + @Override + public boolean onAuthenticated(int fingerId, int groupId) { + boolean result = false; + boolean authenticated = fingerId != 0; + + IFingerprintServiceReceiver receiver = getReceiver(); + if (receiver != null) { + try { + MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH, + authenticated); + if (!authenticated) { + receiver.onAuthenticationFailed(getHalDeviceId()); + } else { + if (DEBUG) { + Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString() + + ", id=" + fingerId + ", gp=" + groupId + ")"); + } + Fingerprint fp = !getIsRestricted() + ? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId()) + : null; + receiver.onAuthenticationSucceeded(getHalDeviceId(), fp); + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify Authenticated:", e); + result = true; // client failed + } + } else { + result = true; // client not listening + } + if (fingerId == 0) { + if (receiver != null) { + FingerprintUtils.vibrateFingerprintError(getContext()); + } + // allow system-defined limit of number of attempts before giving up + result |= handleFailedAttempt(); + } else { + if (receiver != null) { + FingerprintUtils.vibrateFingerprintSuccess(getContext()); + } + result |= true; // we have a valid fingerprint, done + resetFailedAttempts(); + } + return result; + } + + /** + * Start authentication + */ + @Override + public int start() { + IFingerprintDaemon daemon = getFingerprintDaemon(); + if (daemon == null) { + Slog.w(TAG, "start authentication: no fingeprintd!"); + return ERROR_ESRCH; + } + try { + final int result = daemon.authenticate(mOpId, getGroupId()); + if (result != 0) { + Slog.w(TAG, "startAuthentication failed, result=" + result); + onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); + return result; + } + if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating..."); + } catch (RemoteException e) { + Slog.e(TAG, "startAuthentication failed", e); + return ERROR_ESRCH; + } + return 0; // success + } + + @Override + public int stop(boolean initiatedByClient) { + IFingerprintDaemon daemon = getFingerprintDaemon(); + if (daemon == null) { + Slog.w(TAG, "stopAuthentication: no fingeprintd!"); + return ERROR_ESRCH; + } + try { + final int result = daemon.cancelAuthentication(); + if (result != 0) { + Slog.w(TAG, "stopAuthentication failed, result=" + result); + return result; + } + if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating"); + } catch (RemoteException e) { + Slog.e(TAG, "stopAuthentication failed", e); + return ERROR_ESRCH; + } + return 0; // success + } + + @Override + public boolean onEnrollResult(int fingerId, int groupId, int rem) { + if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!"); + return true; // Invalid for Authenticate + } + + @Override + public boolean onRemoved(int fingerId, int groupId) { + if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!"); + return true; // Invalid for Authenticate + } + + @Override + public boolean onEnumerationResult(int fingerId, int groupId) { + if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for authenticate!"); + return true; // Invalid for Authenticate + } +} diff --git a/services/core/java/com/android/server/fingerprint/ClientMonitor.java b/services/core/java/com/android/server/fingerprint/ClientMonitor.java new file mode 100644 index 000000000000..90998edd6d33 --- /dev/null +++ b/services/core/java/com/android/server/fingerprint/ClientMonitor.java @@ -0,0 +1,211 @@ +/** + * Copyright (C) 2016 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.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintDaemon; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.NoSuchElementException; + +/** + * Abstract base class for keeping track and dispatching events from fingerprintd to the + * the current client. Subclasses are responsible for coordinating the interaction with + * fingerprintd 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 fingerprintd is dead. See errno.h. + protected static final boolean DEBUG = FingerprintService.DEBUG; + private IBinder mToken; + private IFingerprintServiceReceiver mReceiver; + private int mUserId; + private int mGroupId; + private boolean mIsRestricted; // True if client does not have MANAGE_FINGERPRINT permission + private String mOwner; + private Context mContext; + private long mHalDeviceId; + + /** + * @param context context of FingerprintService + * @param halDeviceId the HAL device ID of the associated fingerprint hardware + * @param token a unique token for the client + * @param receiver recipient of related events (e.g. authentication) + * @param userId userId for the fingerprint set + * @param groupId groupId for the fingerprint set + * @param restricted whether or not client has the {@link Manifest#MANAGE_FINGERPRINT} + * 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) { + mContext = context; + mHalDeviceId = halDeviceId; + mToken = token; + mReceiver = receiver; + mUserId = userId; + mGroupId = groupId; + mIsRestricted = restricted; + mOwner = owner; + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.w(TAG, "caught remote exception in linkToDeath: ", e); + } + } + + /** + * Contacts fingerprintd to start the client. + * @return 0 on succes, errno from driver on failure + */ + public abstract int start(); + + /** + * Contacts fingerprintd to stop the client. + * @param initiatedByClient whether the operation is at the request of a client + */ + public abstract int stop(boolean initiatedByClient); + + /** + * Method to explicitly poke powermanager on events + */ + public abstract void notifyUserActivity(); + + /** + * Gets the fingerprint daemon from the cached state in the container class. + */ + public abstract IFingerprintDaemon 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). + 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); + public abstract boolean onEnumerationResult(int fingerId, int groupId); + + /** + * Called when we get notification from fingerprintd 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) { + if (mReceiver == null) + return true; // client not connected + try { + mReceiver.onAcquired(getHalDeviceId(), acquiredInfo); + return false; // acquisition continues... + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke sendAcquired:", e); + return true; // client failed + } finally { + // Good scans will keep the device awake + if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) { + notifyUserActivity(); + } + } + } + + /** + * Called when we get notification from fingerprintd 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) { + if (mReceiver != null) { + try { + mReceiver.onError(getHalDeviceId(), error); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke sendError:", e); + } + } + return true; // errors always remove current client + } + + public void destroy() { + if (mToken != null) { + try { + mToken.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + // TODO: remove when duplicate call bug is found + Slog.e(TAG, "destroy(): " + this + ":", new Exception("here")); + } + mToken = null; + } + mReceiver = null; + } + + @Override + public void binderDied() { + mToken = null; + mReceiver = null; + onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); + } + + @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); + } + } finally { + super.finalize(); + } + } + + public final Context getContext() { + return mContext; + } + + public final long getHalDeviceId() { + return mHalDeviceId; + } + + public final String getOwnerString() { + return mOwner; + } + + public final IFingerprintServiceReceiver getReceiver() { + return mReceiver; + } + + public final boolean getIsRestricted() { + return mIsRestricted; + } + + public final int getUserId() { + return mUserId; + } + + public final int getGroupId() { + return mGroupId; + } + + public final IBinder getToken() { + return mToken; + } +} diff --git a/services/core/java/com/android/server/fingerprint/EnrollClient.java b/services/core/java/com/android/server/fingerprint/EnrollClient.java new file mode 100644 index 000000000000..ce5b89080fb4 --- /dev/null +++ b/services/core/java/com/android/server/fingerprint/EnrollClient.java @@ -0,0 +1,136 @@ +/** + * Copyright (C) 2016 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.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintDaemon; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.MetricsProto.MetricsEvent; + +import java.util.Arrays; + +/** + * A class to keep track of the enrollment state for a given client. + */ +public abstract class EnrollClient extends ClientMonitor { + private static final long MS_PER_SEC = 1000; + 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); + mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); + } + + @Override + public boolean onEnrollResult(int fingerId, int groupId, int remaining) { + if (remaining == 0) { + FingerprintUtils.getInstance().addFingerprintForUser(getContext(), fingerId, + getUserId()); + } + return sendEnrollResult(fingerId, groupId, remaining); + } + + /* + * @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 + + FingerprintUtils.vibrateFingerprintSuccess(getContext()); + MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_ENROLL); + try { + receiver.onEnrollResult(getHalDeviceId(), fpId, groupId, remaining); + return remaining == 0; + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify EnrollResult:", e); + return true; + } + } + + @Override + public int start() { + IFingerprintDaemon daemon = getFingerprintDaemon(); + if (daemon == null) { + Slog.w(TAG, "enroll: no fingeprintd!"); + return ERROR_ESRCH; + } + final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); + try { + final int result = daemon.enroll(mCryptoToken, getGroupId(), timeout); + if (result != 0) { + Slog.w(TAG, "startEnroll failed, result=" + result); + onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); + return result; + } + } catch (RemoteException e) { + Slog.e(TAG, "startEnroll failed", e); + } + return 0; // success + } + + @Override + public int stop(boolean initiatedByClient) { + IFingerprintDaemon daemon = getFingerprintDaemon(); + if (daemon == null) { + Slog.w(TAG, "stopEnrollment: no fingeprintd!"); + return ERROR_ESRCH; + } + try { + final int result = daemon.cancelEnrollment(); + if (result != 0) { + Slog.w(TAG, "startEnrollCancel failed, result = " + result); + return result; + } + } catch (RemoteException e) { + Slog.e(TAG, "stopEnrollment failed", e); + } + if (initiatedByClient) { + onError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); + } + return 0; + } + + @Override + public boolean onRemoved(int fingerId, int groupId) { + if (DEBUG) Slog.w(TAG, "onRemoved() called for enroll!"); + return true; // Invalid for EnrollClient + } + + @Override + public boolean onEnumerationResult(int fingerId, int groupId) { + if (DEBUG) Slog.w(TAG, "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!"); + return true; // Invalid for EnrollClient + } + +} diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java new file mode 100644 index 000000000000..b2e4099451ca --- /dev/null +++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2016 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.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintDaemon; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +/** + * 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 userId, int groupId, + boolean restricted, String owner) { + super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner); + } + + @Override + public int start() { + IFingerprintDaemon daemon = getFingerprintDaemon(); + // The fingerprint template ids will be removed when we get confirmation from the HAL + try { + final int result = daemon.enumerate(); + if (result != 0) { + Slog.w(TAG, "start enumerate for user " + getUserId() + + " failed, result=" + result); + onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); + return result; + } + } catch (RemoteException e) { + Slog.e(TAG, "startRemove failed", e); + } + return 0; + } + + @Override + public int stop(boolean initiatedByClient) { + IFingerprintDaemon daemon = getFingerprintDaemon(); + if (daemon == null) { + Slog.w(TAG, "stopAuthentication: no fingeprintd!"); + return ERROR_ESRCH; + } + try { + final int result = daemon.cancelEnumeration(); + if (result != 0) { + Slog.w(TAG, "stop enumeration failed, result=" + result); + return result; + } + } catch (RemoteException e) { + Slog.e(TAG, "stop enumeration 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); + } + return 0; // success + } + + @Override + public boolean onEnumerationResult(int fingerId, int groupId) { + IFingerprintServiceReceiver receiver = getReceiver(); + if (receiver == null) + return true; // client not listening + try { + receiver.onRemoved(getHalDeviceId(), fingerId, groupId); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify enumerated:", e); + } + return fingerId == 0; // done when id hits 0 + } +} diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index e3f3849c79d3..c4da84814b2e 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -19,7 +19,6 @@ package com.android.server.fingerprint; import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; -import android.app.trust.TrustManager; import android.app.ActivityManagerNative; import android.app.AlarmManager; import android.app.AppOpsManager; @@ -48,7 +47,6 @@ import android.os.UserManager; import android.util.Slog; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.server.SystemService; import org.json.JSONArray; @@ -75,7 +73,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.NoSuchElementException; /** * A service to manage multiple clients that want to access the fingerprint HAL API. @@ -85,31 +82,36 @@ import java.util.NoSuchElementException; * @hide */ public class FingerprintService extends SystemService implements IBinder.DeathRecipient { - private static final String TAG = "FingerprintService"; - private static final boolean DEBUG = true; + 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 int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute private static final String ACTION_LOCKOUT_RESET = "com.android.server.fingerprint.ACTION_LOCKOUT_RESET"; - private ClientMonitor mAuthClient = null; - private ClientMonitor mEnrollClient = null; - private ClientMonitor mRemoveClient = null; private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors = new ArrayList<>(); private final AppOpsManager mAppOps; - private static final long MS_PER_SEC = 1000; private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000; private static final int MAX_FAILED_ATTEMPTS = 5; - private static final int FINGERPRINT_ACQUIRED_GOOD = 0; + 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 int mUserIdForRemove = UserHandle.USER_NULL; + 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; - Handler mHandler = new Handler() { + private Handler mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { @@ -123,15 +125,6 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } }; - 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 final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -148,6 +141,26 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } }; + 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; @@ -203,64 +216,49 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe // TODO: update fingerprint/name pairs } - protected void handleRemoved(long deviceId, int fingerId, int groupId) { - final ClientMonitor client = mRemoveClient; - if (fingerId != 0) { - removeTemplateForUser(mUserIdForRemove, fingerId); - } else { - mUserIdForRemove = UserHandle.USER_NULL; - } - if (client != null && client.sendRemoved(fingerId, groupId)) { + 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 handleError(long deviceId, int error) { - if (mEnrollClient != null) { - final IBinder token = mEnrollClient.token; - if (mEnrollClient.sendError(error)) { - stopEnrollment(token, false); - } - } else if (mAuthClient != null) { - final IBinder token = mAuthClient.token; - if (mAuthClient.sendError(error)) { - stopAuthentication(token, false); - } - } else if (mRemoveClient != null) { - if (mRemoveClient.sendError(error)) removeClient(mRemoveClient); + 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) { - if (mAuthClient != null) { - final IBinder token = mAuthClient.token; - if (mAuthClient.sendAuthenticated(fingerId, groupId)) { - stopAuthentication(token, false); - removeClient(mAuthClient); - } + ClientMonitor client = mCurrentClient; + if (client != null && client.onAuthenticated(fingerId, groupId)) { + removeClient(client); } } protected void handleAcquired(long deviceId, int acquiredInfo) { - if (mEnrollClient != null) { - if (mEnrollClient.sendAcquired(acquiredInfo)) { - removeClient(mEnrollClient); - } - } else if (mAuthClient != null) { - if (mAuthClient.sendAcquired(acquiredInfo)) { - removeClient(mAuthClient); - } + ClientMonitor client = mCurrentClient; + if (client != null && client.onAcquired(acquiredInfo)) { + removeClient(client); } } protected void handleEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { - if (mEnrollClient != null) { - if (mEnrollClient.sendEnrollResult(fingerId, groupId, remaining)) { - if (remaining == 0) { - addTemplateForUser(mEnrollClient, fingerId); - removeClient(mEnrollClient); - } - } + ClientMonitor client = mCurrentClient; + if (client != null && client.onEnrollResult(fingerId, groupId, remaining)) { + removeClient(client); } } @@ -274,14 +272,16 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } private void removeClient(ClientMonitor client) { - if (client == null) return; - client.destroy(); - if (client == mAuthClient) { - mAuthClient = null; - } else if (client == mEnrollClient) { - mEnrollClient = null; - } else if (client == mRemoveClient) { - mRemoveClient = null; + 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; } } @@ -303,60 +303,6 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT); } - private 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 boolean handleFailedAttempt(ClientMonitor clientMonitor) { - mFailedAttempts++; - if (inLockoutMode()) { - // Failing multiple times will continue to push out the lockout time. - scheduleLockoutReset(); - if (clientMonitor != null - && !clientMonitor.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { - Slog.w(TAG, "Cannot send lockout message to client"); - } - return true; - } - return false; - } - - private void removeTemplateForUser(int userId, int fingerId) { - mFingerprintUtils.removeFingerprintIdForUser(mContext, fingerId, userId); - } - - private void addTemplateForUser(ClientMonitor clientMonitor, int fingerId) { - mFingerprintUtils.addFingerprintForUser(mContext, fingerId, clientMonitor.userId); - } - - void startEnrollment(IBinder token, byte[] cryptoToken, int groupId, - IFingerprintServiceReceiver receiver, int flags, boolean restricted) { - IFingerprintDaemon daemon = getFingerprintDaemon(); - if (daemon == null) { - Slog.w(TAG, "enroll: no fingeprintd!"); - return; - } - stopPendingOperations(true); - mEnrollClient = new ClientMonitor(token, receiver, groupId, restricted, token.toString()); - final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); - try { - final int result = daemon.enroll(cryptoToken, groupId, timeout); - if (result != 0) { - Slog.w(TAG, "startEnroll failed, result=" + result); - handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); - } - } catch (RemoteException e) { - Slog.e(TAG, "startEnroll failed", e); - } - } - public long startPreEnroll(IBinder token) { IFingerprintDaemon daemon = getFingerprintDaemon(); if (daemon == null) { @@ -385,123 +331,52 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return 0; } - private void stopPendingOperations(boolean initiatedByClient) { - if (mEnrollClient != null) { - stopEnrollment(mEnrollClient.token, initiatedByClient); - } - if (mAuthClient != null) { - stopAuthentication(mAuthClient.token, initiatedByClient); - } - // mRemoveClient is allowed to continue - } - - /** - * Stop enrollment in progress and inform client if they initiated it. - * - * @param token token for client - * @param initiatedByClient if this call is the result of client action (e.g. calling cancel) - */ - void stopEnrollment(IBinder token, boolean initiatedByClient) { - IFingerprintDaemon daemon = getFingerprintDaemon(); - if (daemon == null) { - Slog.w(TAG, "stopEnrollment: no fingeprintd!"); - return; - } - final ClientMonitor client = mEnrollClient; - if (client == null || client.token != token) return; - if (initiatedByClient) { - try { - int result = daemon.cancelEnrollment(); - if (result != 0) { - Slog.w(TAG, "startEnrollCancel failed, result = " + result); - } - } catch (RemoteException e) { - Slog.e(TAG, "stopEnrollment failed", e); - } - client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); - } - removeClient(mEnrollClient); - } - - void startAuthentication(IBinder token, long opId, int realUserId, int groupId, - IFingerprintServiceReceiver receiver, int flags, boolean restricted, - String opPackageName) { - IFingerprintDaemon daemon = getFingerprintDaemon(); - if (daemon == null) { - Slog.w(TAG, "startAuthentication: no fingeprintd!"); - return; - } - stopPendingOperations(true); - updateActiveGroup(groupId, opPackageName); - mAuthClient = new ClientMonitor(token, receiver, groupId, restricted, opPackageName); - if (inLockoutMode()) { - Slog.v(TAG, "In lockout mode; disallowing authentication"); - if (!mAuthClient.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { - Slog.w(TAG, "Cannot send timeout message to client"); - } - mAuthClient = null; - return; - } - try { - final int result = daemon.authenticate(opId, groupId); - if (result != 0) { - Slog.w(TAG, "startAuthentication failed, result=" + result); - handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); - } - } catch (RemoteException e) { - Slog.e(TAG, "startAuthentication failed", e); - } - } - /** - * Stop authentication in progress and inform client if they initiated it. - * - * @param token token for client - * @param initiatedByClient if this call is the result of client action (e.g. calling cancel) + * 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 */ - void stopAuthentication(IBinder token, boolean initiatedByClient) { - IFingerprintDaemon daemon = getFingerprintDaemon(); - if (daemon == null) { - Slog.w(TAG, "stopAuthentication: no fingeprintd!"); - return; - } - final ClientMonitor client = mAuthClient; - if (client == null || client.token != token) return; - if (initiatedByClient) { - try { - int result = daemon.cancelAuthentication(); - if (result != 0) { - Slog.w(TAG, "stopAuthentication failed, result=" + result); - } - } catch (RemoteException e) { - Slog.e(TAG, "stopAuthentication failed", e); - } - client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); + 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(); } - removeClient(mAuthClient); } - void startRemove(IBinder token, int fingerId, int userId, + void startRemove(IBinder token, int fingerId, int userId, int groupId, 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, userId, groupId, fingerId, restricted, token.toString()) { + @Override + public void notifyUserActivity() { + FingerprintService.this.userActivity(); + } - stopPendingOperations(true); - mRemoveClient = new ClientMonitor(token, receiver, userId, restricted, token.toString()); - mUserIdForRemove = mCurrentUserId; - // The fingerprint template ids will be removed when we get confirmation from the HAL - try { - final int result = daemon.remove(fingerId, userId); - if (result != 0) { - Slog.w(TAG, "startRemove with id = " + fingerId + " failed, result=" + result); - handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); + @Override + public IFingerprintDaemon getFingerprintDaemon() { + FingerprintService.this.getFingerprintDaemon(); + return null; } - } catch (RemoteException e) { - Slog.e(TAG, "startRemove failed", e); - } + }; + startClient(client, true); } public List<Fingerprint> getEnrolledFingerprints(int userId) { @@ -572,10 +447,9 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe * @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) { + private boolean canUseFingerprint(String opPackageName, boolean foregroundOnly, int uid, + int pid) { checkPermission(USE_FINGERPRINT); - final int uid = Binder.getCallingUid(); - final int pid = Binder.getCallingPid(); if (isKeyguard(opPackageName)) { return true; // Keyguard is always allowed } @@ -620,165 +494,83 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } - private class ClientMonitor implements IBinder.DeathRecipient { - IBinder token; - IFingerprintServiceReceiver receiver; - int userId; - boolean restricted; // True if client does not have MANAGE_FINGERPRINT permission - String owner; - - public ClientMonitor(IBinder token, IFingerprintServiceReceiver receiver, int userId, - boolean restricted, String owner) { - this.token = token; - this.receiver = receiver; - this.userId = userId; - this.restricted = restricted; - this.owner = owner; // name of the client that owns this - for debugging - try { - token.linkToDeath(this, 0); - } catch (RemoteException e) { - Slog.w(TAG, "caught remote exception in linkToDeath: ", e); - } - } + private void startAuthentication(IBinder token, long opId, int realUserId, int groupId, + IFingerprintServiceReceiver receiver, int flags, boolean restricted, + String opPackageName) { + updateActiveGroup(groupId, opPackageName); - public void destroy() { - if (token != null) { - try { - token.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - // TODO: remove when duplicate call bug is found - Slog.e(TAG, "destroy(): " + this + ":", new Exception("here")); + if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")"); + + AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token, + receiver, realUserId, 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; } - token = null; + return false; } - receiver = null; - } - @Override - public void binderDied() { - token = null; - removeClient(this); - receiver = null; - } - - @Override - protected void finalize() throws Throwable { - try { - if (token != null) { - if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token); - removeClient(this); - } - } finally { - super.finalize(); + @Override + public void resetFailedAttempts() { + FingerprintService.this.resetFailedAttempts(); } - } - /* - * @return true if we're done. - */ - private boolean sendRemoved(int fingerId, int groupId) { - if (receiver == null) return true; // client not listening - try { - receiver.onRemoved(mHalDeviceId, fingerId, groupId); - return fingerId == 0; - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify Removed:", e); + @Override + public void notifyUserActivity() { + FingerprintService.this.userActivity(); } - return false; - } - /* - * @return true if we're done. - */ - private boolean sendEnrollResult(int fpId, int groupId, int remaining) { - if (receiver == null) return true; // client not listening - FingerprintUtils.vibrateFingerprintSuccess(getContext()); - MetricsLogger.action(mContext, MetricsEvent.ACTION_FINGERPRINT_ENROLL); - try { - receiver.onEnrollResult(mHalDeviceId, fpId, groupId, remaining); - return remaining == 0; - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify EnrollResult:", e); - return true; + @Override + public IFingerprintDaemon getFingerprintDaemon() { + return FingerprintService.this.getFingerprintDaemon(); } - } + }; - /* - * @return true if we're done. - */ - private boolean sendAuthenticated(int fpId, int groupId) { - boolean result = false; - boolean authenticated = fpId != 0; - if (receiver != null) { - try { - MetricsLogger.action(mContext, MetricsEvent.ACTION_FINGERPRINT_AUTH, - authenticated); - if (!authenticated) { - receiver.onAuthenticationFailed(mHalDeviceId); - } else { - if (DEBUG) { - Slog.v(TAG, "onAuthenticated(owner=" + mAuthClient.owner - + ", id=" + fpId + ", gp=" + groupId + ")"); - } - Fingerprint fp = !restricted ? - new Fingerprint("" /* TODO */, groupId, fpId, mHalDeviceId) : null; - receiver.onAuthenticationSucceeded(mHalDeviceId, fp); - } - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify Authenticated:", e); - result = true; // client failed - } - } else { - result = true; // client not listening - } - if (fpId == 0) { - if (receiver != null) { - FingerprintUtils.vibrateFingerprintError(getContext()); - } - result |= handleFailedAttempt(this); - } else { - if (receiver != null) { - FingerprintUtils.vibrateFingerprintSuccess(getContext()); - } - result |= true; // we have a valid fingerprint - resetFailedAttempts(); + 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 result; + return; } + startClient(client, true /* initiatedByClient */); + } - /* - * @return true if we're done. - */ - private boolean sendAcquired(int acquiredInfo) { - if (receiver == null) return true; // client not listening - try { - receiver.onAcquired(mHalDeviceId, acquiredInfo); - return false; // acquisition continues... - } catch (RemoteException e) { - Slog.w(TAG, "Failed to invoke sendAcquired:", e); - return true; // client failed - } - finally { - // Good scans will keep the device awake - if (acquiredInfo == FINGERPRINT_ACQUIRED_GOOD) { - userActivity(); - } + private void startEnrollment(IBinder token, byte [] cryptoToken, int userId, int groupId, + IFingerprintServiceReceiver receiver, int flags, boolean restricted, + String opPackageName) { + updateActiveGroup(groupId, opPackageName); + + EnrollClient client = new EnrollClient(getContext(), mHalDeviceId, token, receiver, + userId, groupId, cryptoToken, restricted, opPackageName) { + + @Override + public IFingerprintDaemon getFingerprintDaemon() { + return FingerprintService.this.getFingerprintDaemon(); } - } - /* - * @return true if we're done. - */ - private boolean sendError(int error) { - if (receiver != null) { - try { - receiver.onError(mHalDeviceId, error); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to invoke sendError:", e); - } + @Override + public void notifyUserActivity() { + FingerprintService.this.userActivity(); } - return true; // errors always terminate progress + }; + 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 { @@ -876,8 +668,6 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe }; private final class FingerprintServiceWrapper extends IFingerprintService.Stub { - private static final String KEYGUARD_PACKAGE = "com.android.systemui"; - @Override // Binder call public long preEnroll(IBinder token) { checkPermission(MANAGE_FINGERPRINT); @@ -892,7 +682,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override // Binder call public void enroll(final IBinder token, final byte[] cryptoToken, final int groupId, - final IFingerprintServiceReceiver receiver, final int flags) { + 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); @@ -903,7 +694,6 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe Slog.w(TAG, "Too many fingerprints registered"); return; } - final byte [] cryptoClone = Arrays.copyOf(cryptoToken, cryptoToken.length); // Group ID is arbitrarily set to parent profile user ID. It just represents // the default fingerprints for the user. @@ -915,7 +705,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe mHandler.post(new Runnable() { @Override public void run() { - startEnrollment(token, cryptoClone, groupId, receiver, flags, restricted); + startEnrollment(token, cryptoToken, userId, groupId, receiver, flags, + restricted, opPackageName); } }); } @@ -932,7 +723,10 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe mHandler.post(new Runnable() { @Override public void run() { - stopEnrollment(token, true); + ClientMonitor client = mCurrentClient; + if (client instanceof EnrollClient && client.getToken() == token) { + client.stop(client.getToken() == token); + } } }); } @@ -941,17 +735,18 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe public void authenticate(final IBinder token, final long opId, final int groupId, final IFingerprintServiceReceiver receiver, final int flags, final String opPackageName) { - if (!canUseFingerprint(opPackageName, true /* foregroundOnly */)) { - if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName); - return; - } final int realUserId = Binder.getCallingUid(); - + 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 */, + realUserId, pid)) { + if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName); + return; + } startAuthentication(token, opId, realUserId, groupId, receiver, flags, restricted, opPackageName); } @@ -959,14 +754,29 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } @Override // Binder call - public void cancelAuthentication(final IBinder token, String opPackageName) { - if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { - return; - } + 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() { - stopAuthentication(token, true); + 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()); + } + } } }); } @@ -987,10 +797,11 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe final IFingerprintServiceReceiver receiver) { checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission final boolean restricted = isRestricted(); + final int realUserId = Binder.getCallingUid(); mHandler.post(new Runnable() { @Override public void run() { - startRemove(token, fingerId, groupId, receiver, restricted); + startRemove(token, fingerId, realUserId, groupId, receiver, restricted); } }); @@ -998,7 +809,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override // Binder call public boolean isHardwareDetected(long deviceId, String opPackageName) { - if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { + if (!canUseFingerprint(opPackageName, false /* foregroundOnly */, + Binder.getCallingUid(), Binder.getCallingPid())) { return false; } return mHalDeviceId != 0; @@ -1021,7 +833,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override // Binder call public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) { - if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { + if (!canUseFingerprint(opPackageName, false /* foregroundOnly */, + Binder.getCallingUid(), Binder.getCallingPid())) { return Collections.emptyList(); } if (!isCurrentUserOrProfile(userId)) { @@ -1033,7 +846,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override // Binder call public boolean hasEnrolledFingerprints(int userId, String opPackageName) { - if (!canUseFingerprint(opPackageName, false /* foregroundOnly */)) { + if (!canUseFingerprint(opPackageName, false /* foregroundOnly */, + Binder.getCallingUid(), Binder.getCallingPid())) { return false; } @@ -1061,7 +875,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe // The permission check should be restored once Android Keystore no longer invokes this // method from inside app processes. - return FingerprintService.this.getAuthenticatorId(); + return FingerprintService.this.getAuthenticatorId(opPackageName); } @Override // Binder call @@ -1154,6 +968,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } daemon.setActiveGroup(userId, fpDir.getAbsolutePath().getBytes()); mCurrentUserId = userId; + mCurrentAuthenticatorId = daemon.getAuthenticatorId(); } } catch (RemoteException e) { Slog.e(TAG, "Failed to setActiveGroup():", e); @@ -1204,14 +1019,12 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } - public long getAuthenticatorId() { - IFingerprintDaemon daemon = getFingerprintDaemon(); - if (daemon != null) { - try { - return daemon.getAuthenticatorId(); - } catch (RemoteException e) { - Slog.e(TAG, "getAuthenticatorId failed", e); - } + public long getAuthenticatorId(String opPackageName) { + if (canUseFingerprint(opPackageName, false /* foregroundOnly */, + Binder.getCallingUid(), Binder.getCallingPid())) { + return mCurrentAuthenticatorId; + } else { + Slog.w(TAG, "Client isn't current, returning authenticator_id=0"); } return 0; } diff --git a/services/core/java/com/android/server/fingerprint/RemovalClient.java b/services/core/java/com/android/server/fingerprint/RemovalClient.java new file mode 100644 index 000000000000..69a96e127ef7 --- /dev/null +++ b/services/core/java/com/android/server/fingerprint/RemovalClient.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2016 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.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintDaemon; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Slog; + +/** + * A class to keep track of the remove state for a given client. + */ +public abstract class RemovalClient extends ClientMonitor { + private int mFingerId; + private int mUserIdForRemove; + + public RemovalClient(Context context, long halDeviceId, IBinder token, + IFingerprintServiceReceiver receiver, int userId, int groupId, int fingerId, + boolean restricted, String owner) { + super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner); + mFingerId = fingerId; + mUserIdForRemove = userId; + } + + @Override + public int start() { + IFingerprintDaemon daemon = getFingerprintDaemon(); + // The fingerprint template ids will be removed when we get confirmation from the HAL + try { + final int result = daemon.remove(mFingerId, getUserId()); + if (result != 0) { + Slog.w(TAG, "startRemove with id = " + mFingerId + " failed, result=" + result); + onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE); + return result; + } + } catch (RemoteException e) { + Slog.e(TAG, "startRemove failed", e); + } + return 0; + } + + @Override + public int stop(boolean initiatedByClient) { + // We don't actually stop remove, but inform the client that the cancel operation succeeded + // so we can start the next operation. + if (initiatedByClient) { + onError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); + } + return 0; + } + + /* + * @return true if we're done. + */ + private boolean sendRemoved(int fingerId, int groupId) { + IFingerprintServiceReceiver receiver = getReceiver(); + if (receiver == null) + return true; // client not listening + try { + receiver.onRemoved(getHalDeviceId(), fingerId, groupId); + return fingerId == 0; + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify Removed:", e); + } + return false; + } + + @Override + public boolean onRemoved(int fingerId, int groupId) { + if (fingerId != 0) { + if (fingerId != mFingerId) + FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(), fingerId, + mUserIdForRemove); + } else { + mUserIdForRemove = UserHandle.USER_NULL; + } + return sendRemoved(fingerId, getGroupId()); + } + + @Override + public boolean onEnrollResult(int fingerId, int groupId, int rem) { + if (DEBUG) Slog.w(TAG, "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!"); + return true; // Invalid for Remove. + } + + @Override + public boolean onEnumerationResult(int fingerId, int groupId) { + if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for remove!"); + return false; // Invalid for Remove. + } + + +} |