diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java index 34f245f19d12d..1b8b89c8bc37e 100644 --- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java +++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java @@ -58,7 +58,7 @@ public abstract class EnumerateClient extends ClientMonitor { public int stop(boolean initiatedByClient) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "stopAuthentication: no fingerprint HAL!"); + Slog.w(TAG, "stopEnumeration: no fingerprint HAL!"); return ERROR_ESRCH; } try { @@ -102,12 +102,12 @@ public abstract class EnumerateClient extends ClientMonitor { @Override public boolean onEnrollResult(int fingerId, int groupId, int rem) { if (DEBUG) Slog.w(TAG, "onEnrollResult() called for enumerate!"); - return true; // Invalid for Remove + return true; // Invalid for Enumerate. } @Override public boolean onRemoved(int fingerId, int groupId, int remaining) { if (DEBUG) Slog.w(TAG, "onRemoved() called for enumerate!"); - return true; // Invalid for Authenticate + return true; // Invalid for Enumerate. } } diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 85f7056ba1adb..9b984c1c8ea7a 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -85,6 +85,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.LinkedList; /** * A service to manage multiple clients that want to access the fingerprint HAL API. @@ -96,6 +97,7 @@ import java.util.concurrent.CopyOnWriteArrayList; public class FingerprintService extends SystemService implements IHwBinder.DeathRecipient { static final String TAG = "FingerprintService"; static final boolean DEBUG = true; + private static final boolean CLEANUP_UNUSED_FP = false; private static final String FP_DATA_DIR = "fpdata"; private static final int MSG_USER_SWITCHING = 10; private static final String ACTION_LOCKOUT_RESET = @@ -134,6 +136,20 @@ public class FingerprintService extends SystemService implements IHwBinder.Death private ClientMonitor mPendingClient; private PerformanceStats mPerformanceStats; + + private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration + private LinkedList mEnumeratingUserIds = new LinkedList<>(); + private ArrayList mUnknownFingerprints = new ArrayList<>(); // hw finterprints + + private class UserFingerprint { + Fingerprint f; + int userId; + public UserFingerprint(Fingerprint f, int userId) { + this.f = f; + this.userId = userId; + } + } + // Normal fingerprint authentications are tracked by mPerformanceMap. private HashMap mPerformanceMap = new HashMap<>(); @@ -185,6 +201,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null") + " failed to respond to cancel, starting client " + (mPendingClient != null ? mPendingClient.getOwnerString() : "null")); + mCurrentClient = null; startClient(mPendingClient, false); } @@ -239,6 +256,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death if (mHalDeviceId != 0) { loadAuthenticatorIds(); updateActiveGroup(ActivityManager.getCurrentUser(), null); + doFingerprintCleanup(ActivityManager.getCurrentUser()); } else { Slog.w(TAG, "Failed to open Fingerprint HAL!"); MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1); @@ -253,7 +271,6 @@ public class FingerprintService extends SystemService implements IHwBinder.Death // This operation can be expensive, so keep track of the elapsed time. Might need to move to // background if it takes too long. long t = System.currentTimeMillis(); - mAuthenticatorIds.clear(); for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) { int userId = getUserOrWorkProfileId(null, user.id); @@ -268,14 +285,88 @@ public class FingerprintService extends SystemService implements IHwBinder.Death } } + private void doFingerprintCleanup(int userId) { + if (CLEANUP_UNUSED_FP) { + resetEnumerateState(); + mEnumeratingUserIds.push(userId); + enumerateNextUser(); + } + } + + private void resetEnumerateState() { + if (DEBUG) Slog.v(TAG, "Enumerate cleaning up"); + mEnumeratingUserIds.clear(); + mUnknownFingerprints.clear(); + } + + private void enumerateNextUser() { + int nextUser = mEnumeratingUserIds.getFirst(); + updateActiveGroup(nextUser, null); + boolean restricted = !hasPermission(MANAGE_FINGERPRINT); + + if (DEBUG) Slog.v(TAG, "Enumerating user id " + nextUser + " of " + + mEnumeratingUserIds.size() + " remaining users"); + + startEnumerate(mToken, nextUser, null, restricted, true /* internal */); + } + + // Remove unknown fingerprints from hardware + private void cleanupUnknownFingerprints() { + if (!mUnknownFingerprints.isEmpty()) { + Slog.w(TAG, "unknown fingerprint size: " + mUnknownFingerprints.size()); + UserFingerprint uf = mUnknownFingerprints.get(0); + mUnknownFingerprints.remove(uf); + boolean restricted = !hasPermission(MANAGE_FINGERPRINT); + updateActiveGroup(uf.userId, null); + startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null, + restricted, true /* internal */); + } else { + resetEnumerateState(); + } + } + protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) { - if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId + ", gid=" - + groupId + "rem=" + remaining); - // TODO: coordinate names with framework + if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId + + ", gid=" + groupId + + ", dev=" + deviceId + + ", rem=" + remaining); + + ClientMonitor client = mCurrentClient; + + if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) { + return; + } + client.onEnumerationResult(fingerId, groupId, remaining); + + // All fingerprints in hardware for this user were enumerated + if (remaining == 0) { + mEnumeratingUserIds.poll(); + + if (client instanceof InternalEnumerateClient) { + List enrolled = ((InternalEnumerateClient) client).getEnumeratedList(); + Slog.w(TAG, "Added " + enrolled.size() + " enumerated fingerprints for deletion"); + for (Fingerprint f : enrolled) { + mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId())); + } + } + + removeClient(client); + + if (!mEnumeratingUserIds.isEmpty()) { + enumerateNextUser(); + } else if (client instanceof InternalEnumerateClient) { + if (DEBUG) Slog.v(TAG, "Finished enumerating all users"); + // This will start a chain of InternalRemovalClients + cleanupUnknownFingerprints(); + } + } } protected void handleError(long deviceId, int error, int vendorCode) { ClientMonitor client = mCurrentClient; + if (client instanceof InternalRemovalClient || client instanceof InternalEnumerateClient) { + resetEnumerateState(); + } if (client != null && client.onError(error, vendorCode)) { removeClient(client); } @@ -301,10 +392,20 @@ public class FingerprintService extends SystemService implements IHwBinder.Death } protected void handleRemoved(long deviceId, int fingerId, int groupId, int remaining) { + if (DEBUG) Slog.w(TAG, "Removed: fid=" + fingerId + + ", gid=" + groupId + + ", dev=" + deviceId + + ", rem=" + remaining); + ClientMonitor client = mCurrentClient; if (client != null && client.onRemoved(fingerId, groupId, remaining)) { removeClient(client); } + if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) { + cleanupUnknownFingerprints(); + } else if (client instanceof InternalRemovalClient){ + resetEnumerateState(); + } } protected void handleAuthenticated(long deviceId, int fingerId, int groupId, @@ -355,6 +456,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death void handleUserSwitching(int userId) { updateActiveGroup(userId, null); + doFingerprintCleanup(userId); } private void removeClient(ClientMonitor client) { @@ -431,7 +533,15 @@ public class FingerprintService extends SystemService implements IHwBinder.Death ClientMonitor currentClient = mCurrentClient; if (currentClient != null) { if (DEBUG) Slog.v(TAG, "request stop current client " + currentClient.getOwnerString()); - currentClient.stop(initiatedByClient); + if (currentClient instanceof InternalEnumerateClient || + currentClient instanceof InternalRemovalClient) { + // This condition means we're currently running internal diagnostics to + // remove extra fingerprints in the hardware and/or the software + // TODO: design an escape hatch in case client never finishes + } + else { + currentClient.stop(initiatedByClient); + } mPendingClient = newClient; mHandler.removeCallbacks(mResetClientState); mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT); @@ -448,47 +558,86 @@ public class FingerprintService extends SystemService implements IHwBinder.Death } void startRemove(IBinder token, int fingerId, int groupId, int userId, - IFingerprintServiceReceiver receiver, boolean restricted) { + IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "startRemove: no fingerprint HAL!"); return; } - RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token, - receiver, fingerId, groupId, userId, restricted, token.toString()) { - @Override - public void notifyUserActivity() { - FingerprintService.this.userActivity(); - } - @Override - public IBiometricsFingerprint getFingerprintDaemon() { - return FingerprintService.this.getFingerprintDaemon(); - } - }; - startClient(client, true); + if (internal) { + Context context = getContext(); + InternalRemovalClient client = new InternalRemovalClient(context, mHalDeviceId, + token, receiver, fingerId, groupId, userId, restricted, + context.getOpPackageName()) { + @Override + public void notifyUserActivity() { + + } + @Override + public IBiometricsFingerprint getFingerprintDaemon() { + return FingerprintService.this.getFingerprintDaemon(); + } + }; + startClient(client, true); + } + else { + RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token, + receiver, fingerId, groupId, userId, restricted, token.toString()) { + @Override + public void notifyUserActivity() { + FingerprintService.this.userActivity(); + } + + @Override + public IBiometricsFingerprint getFingerprintDaemon() { + return FingerprintService.this.getFingerprintDaemon(); + } + }; + startClient(client, true); + } } void startEnumerate(IBinder token, int userId, - IFingerprintServiceReceiver receiver, boolean restricted) { + IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "startEnumerate: no fingerprint HAL!"); return; } - EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token, - receiver, userId, userId, restricted, token.toString()) { - @Override - public void notifyUserActivity() { - FingerprintService.this.userActivity(); - } + if (internal) { + List enrolledList = getEnrolledFingerprints(userId); + Context context = getContext(); + InternalEnumerateClient client = new InternalEnumerateClient(context, mHalDeviceId, + token, receiver, userId, userId, restricted, context.getOpPackageName(), + enrolledList) { + @Override + public void notifyUserActivity() { - @Override - public IBiometricsFingerprint getFingerprintDaemon() { - return FingerprintService.this.getFingerprintDaemon(); - } - }; - startClient(client, true); + } + + @Override + public IBiometricsFingerprint getFingerprintDaemon() { + return FingerprintService.this.getFingerprintDaemon(); + } + }; + startClient(client, true); + } + else { + EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token, + receiver, userId, userId, restricted, token.toString()) { + @Override + public void notifyUserActivity() { + FingerprintService.this.userActivity(); + } + + @Override + public IBiometricsFingerprint getFingerprintDaemon() { + return FingerprintService.this.getFingerprintDaemon(); + } + }; + startClient(client, true); + } } public List getEnrolledFingerprints(int userId) { @@ -978,11 +1127,13 @@ public class FingerprintService extends SystemService implements IHwBinder.Death mHandler.post(new Runnable() { @Override public void run() { - startRemove(token, fingerId, groupId, userId, receiver, restricted); + startRemove(token, fingerId, groupId, userId, receiver, + restricted, false /* internal */); } }); } + @Override // Binder call public void enumerate(final IBinder token, final int userId, final IFingerprintServiceReceiver receiver) { checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission @@ -990,7 +1141,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death mHandler.post(new Runnable() { @Override public void run() { - startEnumerate(token, userId, receiver, restricted); + startEnumerate(token, userId, receiver, restricted, false /* internal */); } }); } diff --git a/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java new file mode 100644 index 0000000000000..f4d2596c85bf2 --- /dev/null +++ b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.fingerprint; + +import android.content.Context; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.IBinder; +import android.util.Slog; +import java.util.ArrayList; +import java.util.List; + +/** + * An internal class to help clean up unknown fingerprints in the hardware and software + */ +public abstract class InternalEnumerateClient extends EnumerateClient { + + private List mEnrolledList; + private List mEnumeratedList = new ArrayList<>(); // list of fp to delete + + public InternalEnumerateClient(Context context, long halDeviceId, IBinder token, + IFingerprintServiceReceiver receiver, int groupId, int userId, + boolean restricted, String owner, List enrolledList) { + + super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner); + mEnrolledList = enrolledList; + } + + private void handleEnumeratedFingerprint(int fingerId, int groupId, int remaining) { + + boolean matched = false; + for (int i=0; i getEnumeratedList() { + return mEnumeratedList; + } + + @Override + public boolean onEnumerationResult(int fingerId, int groupId, int remaining) { + + handleEnumeratedFingerprint(fingerId, groupId, remaining); + if (remaining == 0) { + doFingerprintCleanup(); + } + + return fingerId == 0; // done when id hits 0 + } + +} diff --git a/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java b/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java new file mode 100644 index 0000000000000..19f61feac1f4c --- /dev/null +++ b/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.fingerprint; + +import android.content.Context; +import android.os.IBinder; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import com.android.server.fingerprint.RemovalClient; + +public abstract class InternalRemovalClient extends RemovalClient { + + public InternalRemovalClient(Context context, long halDeviceId, IBinder token, + IFingerprintServiceReceiver receiver, int fingerId, int groupId, int userId, + boolean restricted, String owner) { + + super(context, halDeviceId, token, receiver, fingerId, groupId, userId, restricted, owner); + + } +} diff --git a/services/core/java/com/android/server/fingerprint/RemovalClient.java b/services/core/java/com/android/server/fingerprint/RemovalClient.java index ab1b97284998e..6610634c6759e 100644 --- a/services/core/java/com/android/server/fingerprint/RemovalClient.java +++ b/services/core/java/com/android/server/fingerprint/RemovalClient.java @@ -59,12 +59,23 @@ public abstract class RemovalClient extends ClientMonitor { @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, 0 /* vendorCode */); + IBiometricsFingerprint daemon = getFingerprintDaemon(); + if (daemon == null) { + Slog.w(TAG, "stopRemoval: no fingerprint HAL!"); + return ERROR_ESRCH; } - return 0; + try { + final int result = daemon.cancel(); + if (result != 0) { + Slog.w(TAG, "stopRemoval failed, result=" + result); + return result; + } + if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer removing"); + } catch (RemoteException e) { + Slog.e(TAG, "stopRemoval failed", e); + return ERROR_ESRCH; + } + return 0; // success } /*