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
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 4756b372..271ec79 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -517,7 +517,8 @@
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 9c13523..f40f8a3 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintDaemon.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintDaemon.aidl
@@ -35,4 +35,6 @@
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 43d5577..d7915e3 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -35,7 +35,7 @@
// 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 0000000..d2f015f
--- /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 0000000..90998ed
--- /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 0000000..ce5b890
--- /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 0000000..b2e4099
--- /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 e3f3849..c4da848 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 @@
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.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.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 @@
* @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 @@
}
};
- 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 @@
}
};
+ 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 @@
// 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;
+ protected void handleError(long deviceId, int error) {
+ ClientMonitor client = mCurrentClient;
+ if (client != null && client.onError(error)) {
+ removeClient(client);
}
- if (client != null && client.sendRemoved(fingerId, groupId)) {
+ 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 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 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 @@
}
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 @@
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 @@
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)
+ * 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 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);
+ 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();
}
}
- /**
- * 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)
- */
- 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);
- }
- 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;
}
-
- 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);
+ RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
+ receiver, userId, groupId, fingerId, restricted, token.toString()) {
+ @Override
+ public void notifyUserActivity() {
+ FingerprintService.this.userActivity();
}
- } catch (RemoteException e) {
- Slog.e(TAG, "startRemove failed", e);
- }
+
+ @Override
+ public IFingerprintDaemon getFingerprintDaemon() {
+ FingerprintService.this.getFingerprintDaemon();
+ return null;
+ }
+ };
+ startClient(client, true);
}
public List<Fingerprint> getEnrolledFingerprints(int userId) {
@@ -572,10 +447,9 @@
* @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 @@
}
}
- 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;
+ private void startAuthentication(IBinder token, long opId, int realUserId, int groupId,
+ IFingerprintServiceReceiver receiver, int flags, boolean restricted,
+ String opPackageName) {
+ updateActiveGroup(groupId, opPackageName);
- 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);
- }
- }
+ if (DEBUG) Slog.v(TAG, "startAuthentication(" + 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"));
+ 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
+ public void resetFailedAttempts() {
+ FingerprintService.this.resetFailedAttempts();
+ }
- @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 notifyUserActivity() {
+ FingerprintService.this.userActivity();
}
- }
- /*
- * @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 IFingerprintDaemon getFingerprintDaemon() {
+ return FingerprintService.this.getFingerprintDaemon();
}
- 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;
+ 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 */);
+ }
- /*
- * @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();
- }
- return result;
- }
+ private void startEnrollment(IBinder token, byte [] cryptoToken, int userId, int groupId,
+ IFingerprintServiceReceiver receiver, int flags, boolean restricted,
+ String opPackageName) {
+ updateActiveGroup(groupId, opPackageName);
- /*
- * @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();
- }
- }
- }
+ EnrollClient client = new EnrollClient(getContext(), mHalDeviceId, token, receiver,
+ userId, groupId, cryptoToken, restricted, opPackageName) {
- /*
- * @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 IFingerprintDaemon getFingerprintDaemon() {
+ return FingerprintService.this.getFingerprintDaemon();
}
- return true; // errors always terminate progress
+
+ @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 {
@@ -876,8 +668,6 @@
};
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 @@
@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 @@
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 @@
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 @@
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 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 @@
}
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
// 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 @@
}
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 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 0000000..69a96e1
--- /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.
+ }
+
+
+}