Add fingerprint settings support to the framework
- cleanup thread issue and simplify native FingerprintService methods
- add new permissions and enforce them
- add fingerprint hardware detection API
Change-Id: I87c2243ea2412061f1e85b044138480d0161bcdf
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java
index 178cc8b..494c238 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/service/fingerprint/FingerprintManager.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.BaseBundle;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -31,6 +32,9 @@
import android.util.Log;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A class that coordinates access to the fingerprint hardware.
* @hide
@@ -97,6 +101,15 @@
}
};
+ public static final class FingerprintItem {
+ CharSequence name;
+ int id;
+ FingerprintItem(CharSequence name, int id) {
+ this.name = name;
+ this.id = id;
+ }
+ }
+
/**
* @hide
*/
@@ -248,4 +261,38 @@
private void sendError(int msg, int arg1, int arg2) {
mHandler.obtainMessage(msg, arg1, arg2);
}
+
+ /**
+ * @return list of current fingerprint items
+ * @hide
+ */
+ public List<FingerprintItem> getEnrolledFingerprints() {
+ int[] ids = FingerprintUtils.getFingerprintIdsForUser(mContext.getContentResolver(),
+ getCurrentUserId());
+ List<FingerprintItem> result = new ArrayList<FingerprintItem>();
+ for (int i = 0; i < ids.length; i++) {
+ // TODO: persist names in Settings
+ FingerprintItem item = new FingerprintItem("Finger" + ids[i], ids[i]);
+ result.add(item);
+ }
+ return result;
+ }
+
+ /**
+ * Determine if fingerprint hardware is present and functional.
+ * @return true if hardware is present and functional, false otherwise.
+ * @hide
+ */
+ public boolean isHardwareDetected() {
+ if (mService != null) {
+ try {
+ return mService.isHardwareDetected();
+ } catch (RemoteException e) {
+ Log.v(TAG, "Remote exception in isFingerprintHardwareDetected(): ", e);
+ }
+ } else {
+ Log.w(TAG, "isFingerprintHardwareDetected(): Service not connected!");
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java
index a4caf8e..cc17b99 100644
--- a/core/java/android/service/fingerprint/FingerprintUtils.java
+++ b/core/java/android/service/fingerprint/FingerprintUtils.java
@@ -21,7 +21,11 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* Utility class for dealing with fingerprints and fingerprint settings.
@@ -32,34 +36,50 @@
private static final boolean DEBUG = true;
private static final String TAG = "FingerprintUtils";
+ private static int[] toIntArray(List<Integer> list) {
+ if (list == null) {
+ return null;
+ }
+ int[] arr = new int[list.size()];
+ int i = 0;
+ for (int elem : list) {
+ arr[i] = elem;
+ i++;
+ }
+ return arr;
+ }
+
public static int[] getFingerprintIdsForUser(ContentResolver res, int userId) {
String fingerIdsRaw = Settings.Secure.getStringForUser(res,
Settings.Secure.USER_FINGERPRINT_IDS, userId);
-
- int result[] = {};
+ ArrayList<Integer> tmp = new ArrayList<Integer>();
if (!TextUtils.isEmpty(fingerIdsRaw)) {
String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", ");
- result = new int[fingerStringIds.length];
- for (int i = 0; i < result.length; i++) {
+ int length = fingerStringIds.length;
+ for (int i = 0; i < length; i++) {
try {
- result[i] = Integer.decode(fingerStringIds[i]);
+ tmp.add(Integer.decode(fingerStringIds[i]));
} catch (NumberFormatException e) {
- if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]);
+ if (DEBUG) Log.w(TAG, "Error parsing finger id: '" + fingerStringIds[i] + "'");
}
}
}
- return result;
+ return toIntArray(tmp);
}
public static void addFingerprintIdForUser(int fingerId, ContentResolver res, int userId) {
+ // FingerId 0 has special meaning.
+ if (fingerId == 0) {
+ Log.w(TAG, "Tried to add fingerId 0");
+ return;
+ }
+
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;
+ if (ArrayUtils.contains(fingerIds, fingerId)) {
+ Log.w(TAG, "finger already added " + fingerId);
+ return;
}
int[] newList = Arrays.copyOf(fingerIds, fingerIds.length + 1);
newList[fingerIds.length] = fingerId;
@@ -72,19 +92,13 @@
// 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");
+ if (fingerId == 0) throw new IllegalArgumentException("fingerId can't be 0");
- 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) {
+ final int[] fingerIds = getFingerprintIdsForUser(res, userId);
+ if (ArrayUtils.contains(fingerIds, fingerId)) {
+ final int[] result = ArrayUtils.removeInt(fingerIds, fingerId);
Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS,
- Arrays.toString(Arrays.copyOf(resultIds, resultCount)), userId);
+ Arrays.toString(result), userId);
return true;
}
return false;
diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl
index 43d5e9a..a7d4090 100644
--- a/core/java/android/service/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/service/fingerprint/IFingerprintService.aidl
@@ -22,10 +22,10 @@
* Communication channel from client to the fingerprint service.
* @hide
*/
-oneway interface IFingerprintService {
+interface IFingerprintService {
// Any errors resulting from this call will be returned to the listener
void enroll(IBinder token, long timeout, int userId);
-
+
// Any errors resulting from this call will be returned to the listener
void enrollCancel(IBinder token, int userId);
@@ -38,4 +38,7 @@
// Stops listening for fingerprints
void stopListening(IBinder token, int userId);
+
+ // Determine if HAL is loaded and ready
+ boolean isHardwareDetected();
}
diff --git a/core/jni/android_server_FingerprintManager.cpp b/core/jni/android_server_FingerprintManager.cpp
index 24f8f67..853425c 100644
--- a/core/jni/android_server_FingerprintManager.cpp
+++ b/core/jni/android_server_FingerprintManager.cpp
@@ -20,10 +20,11 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
+#include <android_os_MessageQueue.h>
#include <hardware/hardware.h>
#include <hardware/fingerprint.h>
#include <utils/Log.h>
-
+#include <utils/Looper.h>
#include "core_jni_helpers.h"
namespace android {
@@ -34,7 +35,6 @@
static struct {
jclass clazz;
jmethodID notify;
- jobject callbackObject;
} gFingerprintServiceClassInfo;
static struct {
@@ -42,11 +42,26 @@
fingerprint_device_t *device;
} gContext;
+static sp<Looper> gLooper;
+static jobject gCallback;
+
+class CallbackHandler : public MessageHandler {
+ int type;
+ int arg1, arg2;
+public:
+ CallbackHandler(int type, int arg1, int arg2) : type(type), arg1(arg1), arg2(arg2) { }
+
+ virtual void handleMessage(const Message& message) {
+ //ALOG(LOG_VERBOSE, LOG_TAG, "hal_notify(msg=%d, arg1=%d, arg2=%d)\n", msg.type, arg1, arg2);
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(gCallback, gFingerprintServiceClassInfo.notify, type, arg1, arg2);
+ }
+};
+
// Called by the HAL to notify us of fingerprint events
static void hal_notify_callback(fingerprint_msg_t msg) {
uint32_t arg1 = 0;
uint32_t arg2 = 0;
- uint32_t arg3 = 0; // TODO
switch (msg.type) {
case FINGERPRINT_ERROR:
arg1 = msg.data.error;
@@ -60,7 +75,6 @@
case FINGERPRINT_TEMPLATE_ENROLLING:
arg1 = msg.data.enroll.finger.fid;
arg2 = msg.data.enroll.samples_remaining;
- arg3 = 0;
break;
case FINGERPRINT_TEMPLATE_REMOVED:
arg1 = msg.data.removed.finger.fid;
@@ -69,32 +83,16 @@
ALOGE("fingerprint: invalid msg: %d", msg.type);
return;
}
- (void)arg3;
- //ALOG(LOG_VERBOSE, LOG_TAG, "hal_notify(msg=%d, arg1=%d, arg2=%d)\n", msg.type, arg1, arg2);
-
- // TODO: fix gross hack to attach JNI to calling thread
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
- JavaVM* vm = AndroidRuntime::getJavaVM();
- int result = vm->AttachCurrentThread(&env, (void*) &args);
- if (result != JNI_OK) {
- ALOGE("Can't call JNI method: attach failed: %#x", result);
- return;
- }
- }
- env->CallVoidMethod(gFingerprintServiceClassInfo.callbackObject,
- gFingerprintServiceClassInfo.notify, msg.type, arg1, arg2);
+ // This call potentially comes in on a thread not owned by us. Hand it off to our
+ // looper so it runs on our thread when calling back to FingerprintService.
+ // CallbackHandler object is reference-counted, so no cleanup necessary.
+ gLooper->sendMessage(new CallbackHandler(msg.type, arg1, arg2), Message());
}
-static void nativeInit(JNIEnv *env, jobject clazz, jobject callbackObj) {
+static void nativeInit(JNIEnv *env, jobject clazz, jobject mQueue, jobject callbackObj) {
ALOG(LOG_VERBOSE, LOG_TAG, "nativeInit()\n");
- gFingerprintServiceClassInfo.clazz = FindClassOrDie(env, FINGERPRINT_SERVICE);
- gFingerprintServiceClassInfo.clazz = MakeGlobalRefOrDie(env,
- gFingerprintServiceClassInfo.clazz);
- gFingerprintServiceClassInfo.notify = GetMethodIDOrDie(env, gFingerprintServiceClassInfo.clazz,
- "notify", "(III)V");
- gFingerprintServiceClassInfo.callbackObject = MakeGlobalRefOrDie(env, callbackObj);
+ gCallback = MakeGlobalRefOrDie(env, callbackObj);
+ gLooper = android_os_MessageQueue_getMessageQueue(env, mQueue)->getLooper();
}
static jint nativeEnroll(JNIEnv* env, jobject clazz, jint timeout) {
@@ -179,14 +177,15 @@
{ "nativeRemove", "(I)I", (void*)nativeRemove },
{ "nativeOpenHal", "()I", (void*)nativeOpenHal },
{ "nativeCloseHal", "()I", (void*)nativeCloseHal },
- { "nativeInit", "(Lcom/android/server/fingerprint/FingerprintService;)V", (void*)nativeInit }
+ { "nativeInit","(Landroid/os/MessageQueue;"
+ "Lcom/android/server/fingerprint/FingerprintService;)V", (void*)nativeInit }
};
int register_android_server_fingerprint_FingerprintService(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, FINGERPRINT_SERVICE);
gFingerprintServiceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
- gFingerprintServiceClassInfo.notify = GetMethodIDOrDie(env, gFingerprintServiceClassInfo.clazz,
- "notify", "(III)V");
+ gFingerprintServiceClassInfo.notify =
+ GetMethodIDOrDie(env, gFingerprintServiceClassInfo.clazz,"notify", "(III)V");
int result = RegisterMethodsOrDie(env, FINGERPRINT_SERVICE, g_methods, NELEM(g_methods));
ALOG(LOG_VERBOSE, LOG_TAG, "FingerprintManager JNI ready.\n");
return result;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4b97138..0d15ae54 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2835,6 +2835,18 @@
android:label="@string/permlab_access_keyguard_secure_storage"
android:description="@string/permdesc_access_keyguard_secure_storage" />
+ <!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide -->
+ <permission android:name="android.permission.MANAGE_FINGERPRINT"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_manageFingerprint"
+ android:description="@string/permdesc_manageFingerprint" />
+
+ <!-- Allows an app to use fingerprint hardware. -->
+ <permission android:name="android.permission.USE_FINGERPRINT"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_useFingerprint"
+ android:description="@string/permdesc_useFingerprint" />
+
<!-- Allows an application to control keyguard. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.CONTROL_KEYGUARD"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 199b783..b1f51ef 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2209,6 +2209,15 @@
re-enables the keylock when the call is finished.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_manageFingerprint">manage fingerprint hardware</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_manageFingerprint">Allows the app to invoke methods to add and delete fingerprint templates for use.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_useFingerprint">use fingerprint hardware</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_useFingerprint">Allows the app to use fingerprint hardware for authentication</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readSyncSettings">read sync settings</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_readSyncSettings">Allows the app to read the sync settings for an account. For example, this can determine whether the People app is synced with an account.</string>