diff options
author | Jim Miller <jaggies@google.com> | 2014-04-29 18:18:47 -0700 |
---|---|---|
committer | Jim Miller <jaggies@google.com> | 2014-05-07 02:27:21 +0000 |
commit | 08fa40c5cb5229b7969b2a5146855a337870f45a (patch) | |
tree | 7020af1c292cfc0312973d56d4037b78ee941c72 /core/java/android/service/fingerprint | |
parent | 6c8e788e37dea5db682e2ad405f8e1b23990765e (diff) | |
download | frameworks_base-08fa40c5cb5229b7969b2a5146855a337870f45a.zip frameworks_base-08fa40c5cb5229b7969b2a5146855a337870f45a.tar.gz frameworks_base-08fa40c5cb5229b7969b2a5146855a337870f45a.tar.bz2 |
First pass at adding FingerprintManagerService
This adds a new service for monitoring and enrolling fingerprints
to the platform.
Fixed documentation links.
Change-Id: I66013be5e09be9c5f9746c46aacf32d3e26c3b73
Diffstat (limited to 'core/java/android/service/fingerprint')
6 files changed, 631 insertions, 0 deletions
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java new file mode 100644 index 0000000..0d14c59 --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -0,0 +1,200 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.fingerprint; + +import android.app.ActivityManagerNative; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +/** + * A class that coordinates access to the fingerprint hardware. + */ + +public class FingerprintManager { + private static final String TAG = "FingerprintManager"; + protected static final boolean DEBUG = true; + private static final String FINGERPRINT_SERVICE_PACKAGE = "com.android.service.fingerprint"; + private static final String FINGERPRINT_SERVICE_CLASS = + "com.android.service.fingerprint.FingerprintService"; + private static final int MSG_ENROLL_RESULT = 100; + private static final int MSG_SCANNED = 101; + private static final int MSG_ERROR = 102; + private static final int MSG_REMOVED = 103; + + public static final int FINGERPRINT_ERROR_NO_RECEIVER = -10; + public static final int FINGERPRINT_ERROR = -1; // One of the error messages below. + + // Progress messages. + public static final int FINGERPRINT_SCANNED = 1; + public static final int FINGERPRINT_TEMPLATE_ENROLLING = 2; + public static final int FINGERPRINT_TEMPLATE_REMOVED = 4; + + // Error messages. Must agree with fingerprint HAL definitions. + public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; + public static final int FINGERPRINT_ERROR_BAD_CAPTURE = 2; + public static final int FINGERPRINT_ERROR_TIMEOUT = 3; + public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + + private IFingerprintService mService; + private FingerprintManagerReceiver mClientReceiver; + + private Handler mHandler = new Handler() { + public void handleMessage(android.os.Message msg) { + if (mClientReceiver != null) { + switch(msg.what) { + case MSG_ENROLL_RESULT: + mClientReceiver.onEnrollResult(msg.arg1, msg.arg2); + break; + case MSG_SCANNED: + mClientReceiver.onScanned(msg.arg1, msg.arg2); + break; + case MSG_ERROR: + mClientReceiver.onError(msg.arg1); + break; + case MSG_REMOVED: + mClientReceiver.onRemoved(msg.arg1); + } + } + } + }; + + public FingerprintManager(Context context) { + // Connect to service... + Intent intent = new Intent(); + intent.setClassName(FINGERPRINT_SERVICE_PACKAGE, FINGERPRINT_SERVICE_CLASS); + if (!context.bindServiceAsUser(intent, mFingerprintConnection, + Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF)) { + if (DEBUG) Log.v(TAG, "Can't bind to " + FINGERPRINT_SERVICE_CLASS); + } + } + + private final ServiceConnection mFingerprintConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) Log.v(TAG, "Connected to FingerprintService"); + mService = IFingerprintService.Stub.asInterface(service); + try { + mService.startListening(mServiceReceiver, getCurrentUserId()); + } catch (RemoteException e) { + if (DEBUG) Log.v(TAG, "Failed to set callback", e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Log.v(TAG, "Disconnected from FingerprintService"); + mService = null; + } + }; + + private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { + + public void onEnrollResult(int fingerprintId, int remaining) { + mHandler.obtainMessage(MSG_ENROLL_RESULT, fingerprintId, remaining).sendToTarget(); + } + + public void onScanned(int fingerprintId, int confidence) { + mHandler.obtainMessage(MSG_SCANNED, fingerprintId, confidence) + .sendToTarget();; + } + + public void onError(int error) { + mHandler.obtainMessage(MSG_ERROR, error, 0).sendToTarget(); + } + + public void onRemoved(int fingerprintId) { + mHandler.obtainMessage(MSG_REMOVED, fingerprintId, 0).sendToTarget(); + } + }; + + /** + * Start the enrollment process. Timeout dictates how long to wait for the user to + * enroll a fingerprint. + * + * @param timeout + */ + public void enroll(long timeout) { + if (mServiceReceiver == null) { + throw new IllegalStateException("enroll: Call registerCallback() first"); + } + if (mService != null) try { + mService.enroll(timeout, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception while enrolling: ", e); + } + } + + /** + * Remove the given fingerprintId from the system. FingerprintId of 0 has special meaning + * which is to delete all fingerprint data for the current user. Use with caution. + * @param fingerprintId + */ + public void remove(int fingerprintId) { + if (mService != null) try { + mService.remove(fingerprintId, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception during remove of fingerprintId: " + fingerprintId, e); + } + } + + /** + * Starts listening for fingerprint events. When a finger is scanned or recognized, the + * client will be notified via the callback. + */ + public void startListening(FingerprintManagerReceiver receiver) { + mClientReceiver = receiver; + if (mService != null) { + try { + mService.startListening(mServiceReceiver, getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in startListening(): ", e); + } + } + } + + private int getCurrentUserId() { + try { + return ActivityManagerNative.getDefault().getCurrentUser().id; + } catch (RemoteException e) { + Log.w(TAG, "Failed to get current user id\n"); + return UserHandle.USER_NULL; + } + } + + /** + * Stops the client from listening to fingerprint events. + */ + public void stopListening() { + mClientReceiver = null; + if (mService != null) { + try { + mService.stopListening(getCurrentUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in stopListening(): ", e); + } + } else { + Log.w(TAG, "stopListening(): Service not connected!"); + } + } +}
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java new file mode 100644 index 0000000..34f1655 --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java @@ -0,0 +1,59 @@ +package android.service.fingerprint; +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class FingerprintManagerReceiver { + /** + * Fingerprint enrollment progress update. Enrollment is considered complete if + * remaining hits 0 without {@link #onError(int)} being called. + * + * @param fingerprintId the fingerprint we're currently enrolling + * @param remaining the number of samples required to complete enrollment. It's up to + * the hardware to define what each step in enrollment means. Some hardware + * requires multiple samples of the same part of the finger. Others require sampling of + * different parts of the finger. The enrollment flow can use remaining to + * mean "step x" of the process or "just need another sample." + */ + public void onEnrollResult(int fingerprintId, int remaining) { } + + /** + * Fingerprint scan detected. Most clients will use this function to detect a fingerprint + * + * @param fingerprintId is the finger the hardware has detected. + * @param confidence from 0 (no confidence) to 65535 (high confidence). Fingerprint 0 has + * special meaning - the finger wasn't recognized. + */ + public void onScanned(int fingerprintId, int confidence) { } + + /** + * An error was detected during scan or enrollment. One of + * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}, + * {@link FingerprintManager#FINGERPRINT_ERROR_BAD_CAPTURE} or + * {@link FingerprintManager#FINGERPRINT_ERROR_TIMEOUT} + * {@link FingerprintManager#FINGERPRINT_ERROR_NO_SPACE} + * + * @param error one of the above error codes + */ + public void onError(int error) { } + + /** + * The given fingerprint template was successfully removed by the driver. + * See {@link FingerprintManager#remove(int)} + * + * @param fingerprintId id of template to remove. + */ + public void onRemoved(int fingerprintId) { } +}
\ No newline at end of file diff --git a/core/java/android/service/fingerprint/FingerprintService.java b/core/java/android/service/fingerprint/FingerprintService.java new file mode 100644 index 0000000..c7fa7cd --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintService.java @@ -0,0 +1,219 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.fingerprint; + +import android.app.Service; +import android.content.ContentResolver; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.Slog; + +import java.io.PrintWriter; +import java.util.HashMap; + +/** + * A service to manage multiple clients that want to access the fingerprint HAL API. + * The service is responsible for maintaining a list of clients and dispatching all + * fingerprint -related events. + * + * @hide + */ +public class FingerprintService extends Service { + private final String TAG = FingerprintService.class.getSimpleName() + + "[" + getClass().getSimpleName() + "]"; + private static final boolean DEBUG = true; + HashMap<IFingerprintServiceReceiver, ClientData> mClients = + new HashMap<IFingerprintServiceReceiver, ClientData>(); + + private static final int MSG_NOTIFY = 10; + + Handler mHandler = new Handler() { + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_NOTIFY: + handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj); + break; + + default: + Slog.w(TAG, "Unknown message:" + msg.what); + } + } + }; + + private static final int STATE_IDLE = 0; + private static final int STATE_LISTENING = 1; + private static final int STATE_ENROLLING = 2; + private static final int STATE_DELETING = 3; + private static final long MS_PER_SEC = 1000; + + private static final class ClientData { + public IFingerprintServiceReceiver receiver; + int state; + int userId; + } + + @Override + public final IBinder onBind(Intent intent) { + if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent); + return new FingerprintServiceWrapper(); + } + + // JNI methods to communicate from FingerprintManagerService to HAL + native int nativeEnroll(int timeout); + native int nativeRemove(int fingerprintId); + + // JNI methods for communicating from HAL to clients + void notify(int msg, int arg1, int arg2) { + mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget(); + } + + void handleNotify(int msg, int arg1, int arg2) { + for (int i = 0; i < mClients.size(); i++) { + ClientData clientData = mClients.get(i); + switch (msg) { + case FingerprintManager.FINGERPRINT_ERROR: { + if (clientData.state != STATE_IDLE) { + // FINGERPRINT_ERROR_HW_UNAVAILABLE + // FINGERPRINT_ERROR_BAD_CAPTURE + // FINGERPRINT_ERROR_TIMEOUT + // FINGERPRINT_ERROR_NO_SPACE + final int error = arg1; + clientData.state = STATE_IDLE; + if (clientData.receiver != null) { + try { + clientData.receiver.onError(error); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + } + } + break; + case FingerprintManager.FINGERPRINT_SCANNED: { + final int fingerId = arg1; + final int confidence = arg2; + if (clientData.state == STATE_LISTENING && clientData.receiver != null) { + try { + clientData.receiver.onScanned(fingerId, confidence); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + break; + } + case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: { + if (clientData.state == STATE_ENROLLING) { + final int fingerId = arg1; + final int remaining = arg2; + if (remaining == 0) { + FingerprintUtils.addFingerprintIdForUser(fingerId, + getContentResolver(), clientData.userId); + clientData.state = STATE_IDLE; // Nothing left to do + } + if (clientData.receiver != null) { + try { + clientData.receiver.onEnrollResult(fingerId, remaining); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + } + break; + } + case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: { + int fingerId = arg1; + if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL"); + if (clientData.state == STATE_DELETING) { + FingerprintUtils.removeFingerprintIdForUser(fingerId, getContentResolver(), + clientData.userId); + if (clientData.receiver != null) { + try { + clientData.receiver.onRemoved(fingerId); + } catch (RemoteException e) { + Slog.e(TAG, "can't send message to client. Did it die?", e); + } + } + } + } + break; + } + } + } + + int enroll(IFingerprintServiceReceiver receiver, long timeout, int userId) { + ClientData clientData = mClients.get(receiver); + if (clientData != null) { + if (clientData.userId != userId) throw new IllegalStateException("Bad user"); + clientData.state = STATE_ENROLLING; + return nativeEnroll((int) (timeout / MS_PER_SEC)); + } + return -1; + } + + int remove(IFingerprintServiceReceiver receiver, int fingerId, int userId) { + ClientData clientData = mClients.get(receiver); + if (clientData != null) { + if (clientData.userId != userId) throw new IllegalStateException("Bad user"); + clientData.state = STATE_DELETING; + // The fingerprint id will be removed when we get confirmation from the HAL + return nativeRemove(fingerId); + } + return -1; + } + + void startListening(IFingerprintServiceReceiver receiver, int userId) { + ClientData clientData = new ClientData(); + clientData.state = STATE_LISTENING; + clientData.receiver = receiver; + clientData.userId = userId; + mClients.put(receiver, clientData); + } + + void stopListening(IFingerprintServiceReceiver receiver, int userId) { + ClientData clientData = mClients.get(receiver); + if (clientData != null) { + clientData.state = STATE_IDLE; + clientData.userId = -1; + clientData.receiver = null; + } + mClients.remove(receiver); + } + + private final class FingerprintServiceWrapper extends IFingerprintService.Stub { + IFingerprintServiceReceiver mReceiver; + public int enroll(long timeout, int userId) { + return mReceiver != null ? FingerprintService.this.enroll(mReceiver, timeout, userId) + : FingerprintManager.FINGERPRINT_ERROR_NO_RECEIVER; + } + + public int remove(int fingerprintId, int userId) { + return FingerprintService.this.remove(mReceiver, fingerprintId, userId); + } + + public void startListening(IFingerprintServiceReceiver receiver, int userId) { + mReceiver = receiver; + FingerprintService.this.startListening(receiver, userId); + } + + public void stopListening(int userId) { + FingerprintService.this.stopListening(mReceiver, userId); + } + } +} diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java new file mode 100644 index 0000000..81a2aac --- /dev/null +++ b/core/java/android/service/fingerprint/FingerprintUtils.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.fingerprint; + +import android.content.ContentResolver; +import android.provider.Settings; +import android.util.Log; + +import java.util.Arrays; + +class FingerprintUtils { + private static final boolean DEBUG = true; + private static final String TAG = "FingerprintUtils"; + + public static int[] getFingerprintIdsForUser(ContentResolver res, int userId) { + String fingerIdsRaw = Settings.Secure.getStringForUser(res, + Settings.Secure.USER_FINGERPRINT_IDS, userId); + + String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", "); + int result[] = new int[fingerStringIds.length]; + for (int i = 0; i < result.length; i++) { + try { + result[i] = Integer.decode(fingerStringIds[i]); + } catch (NumberFormatException e) { + if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]); + } + } + return result; + } + + public static void addFingerprintIdForUser(int fingerId, ContentResolver res, int userId) { + int[] fingerIds = getFingerprintIdsForUser(res, userId); + + // FingerId 0 has special meaning. + if (fingerId == 0) return; + + // Don't allow dups + for (int i = 0; i < fingerIds.length; i++) { + if (fingerIds[i] == fingerId) return; + } + int[] newList = Arrays.copyOf(fingerIds, fingerIds.length + 1); + newList[fingerIds.length] = fingerId; + Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS, + Arrays.toString(newList), userId); + } + + public static boolean removeFingerprintIdForUser(int fingerId, ContentResolver res, int userId) + { + // FingerId 0 has special meaning. The HAL layer is supposed to remove each finger one + // at a time and invoke notify() for each fingerId. If we get called with 0 here, it means + // something bad has happened. + if (fingerId == 0) throw new IllegalStateException("Bad fingerId"); + + int[] fingerIds = getFingerprintIdsForUser(res, userId); + int[] resultIds = Arrays.copyOf(fingerIds, fingerIds.length); + int resultCount = 0; + for (int i = 0; i < fingerIds.length; i++) { + if (fingerId != fingerIds[i]) { + resultIds[resultCount++] = fingerIds[i]; + } + } + if (resultCount > 0) { + Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS, + Arrays.toString(Arrays.copyOf(resultIds, resultCount)), userId); + return true; + } + return false; + } + +}; + diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl new file mode 100644 index 0000000..e92c20c --- /dev/null +++ b/core/java/android/service/fingerprint/IFingerprintService.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.fingerprint; + +import android.os.Bundle; +import android.service.fingerprint.IFingerprintServiceReceiver; + +/** + * Communication channel from client to the fingerprint service. + * @hide + */ +interface IFingerprintService { + // Returns 0 if successfully started, -1 otherwise + int enroll(long timeout, int userId); + + // Returns 0 if fingerprintId's template can be removed, -1 otherwise + int remove(int fingerprintId, int userId); + + // Start listening for fingerprint events. This has the side effect of starting + // the hardware if not already started. + oneway void startListening(IFingerprintServiceReceiver receiver, int userId); + + // Stops listening for fingerprints + oneway void stopListening(int userId); +} diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl new file mode 100644 index 0000000..4826b59 --- /dev/null +++ b/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.fingerprint; + +import android.os.Bundle; +import android.os.UserHandle; + +/** + * Communication channel from the FingerprintService back to FingerprintManager. + * @hide + */ +oneway interface IFingerprintServiceReceiver { + void onEnrollResult(int fingerprintId, int remaining); + void onScanned(int fingerprintId, int confidence); + void onError(int error); + void onRemoved(int fingerprintId); +} |