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.
+    }
+
+
+}