diff --git a/api/current.txt b/api/current.txt index 285d75d663222..5386b04de29d6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -141,6 +141,7 @@ package android { field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT"; field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final java.lang.String USE_CREDENTIALS = "android.permission.USE_CREDENTIALS"; + field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; field public static final java.lang.String VIBRATE = "android.permission.VIBRATE"; field public static final java.lang.String WAKE_LOCK = "android.permission.WAKE_LOCK"; diff --git a/api/system-current.txt b/api/system-current.txt index 039389674dca9..ab53f0b1db49f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -210,6 +210,7 @@ package android { field public static final java.lang.String UPDATE_LOCK = "android.permission.UPDATE_LOCK"; field public static final java.lang.String USER_ACTIVITY = "android.permission.USER_ACTIVITY"; field public static final java.lang.String USE_CREDENTIALS = "android.permission.USE_CREDENTIALS"; + field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; field public static final java.lang.String USE_SIP = "android.permission.USE_SIP"; field public static final java.lang.String VIBRATE = "android.permission.VIBRATE"; field public static final java.lang.String WAKE_LOCK = "android.permission.WAKE_LOCK"; diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java index 178cc8ba531e3..494c2388b185d 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; 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.provider.Settings; 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 class FingerprintManager { } }; + 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 @@ public class FingerprintManager { private void sendError(int msg, int arg1, int arg2) { mHandler.obtainMessage(msg, arg1, arg2); } + + /** + * @return list of current fingerprint items + * @hide + */ + public List getEnrolledFingerprints() { + int[] ids = FingerprintUtils.getFingerprintIdsForUser(mContext.getContentResolver(), + getCurrentUserId()); + List result = new ArrayList(); + 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 a4caf8ea34205..cc17b99adde79 100644 --- a/core/java/android/service/fingerprint/FingerprintUtils.java +++ b/core/java/android/service/fingerprint/FingerprintUtils.java @@ -21,7 +21,11 @@ import android.provider.Settings; 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 @@ class FingerprintUtils { private static final boolean DEBUG = true; private static final String TAG = "FingerprintUtils"; + private static int[] toIntArray(List 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 tmp = new ArrayList(); 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 @@ class FingerprintUtils { // 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 43d5e9a48d137..a7d409099bc50 100644 --- a/core/java/android/service/fingerprint/IFingerprintService.aidl +++ b/core/java/android/service/fingerprint/IFingerprintService.aidl @@ -22,10 +22,10 @@ import android.service.fingerprint.IFingerprintServiceReceiver; * 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 @@ oneway interface IFingerprintService { // 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 24f8f6757eae6..853425c78ecf5 100644 --- a/core/jni/android_server_FingerprintManager.cpp +++ b/core/jni/android_server_FingerprintManager.cpp @@ -20,10 +20,11 @@ #include #include +#include #include #include #include - +#include #include "core_jni_helpers.h" namespace android { @@ -34,7 +35,6 @@ static const char* FINGERPRINT_SERVICE = "com/android/server/fingerprint/Fingerp static struct { jclass clazz; jmethodID notify; - jobject callbackObject; } gFingerprintServiceClassInfo; static struct { @@ -42,11 +42,26 @@ static struct { fingerprint_device_t *device; } gContext; +static sp 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 @@ static void hal_notify_callback(fingerprint_msg_t msg) { 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 @@ static void hal_notify_callback(fingerprint_msg_t msg) { 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 @@ static const JNINativeMethod g_methods[] = { { "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 4b9713846b98e..0d15ae543e0ed 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" /> + + + + + + + + manage fingerprint hardware + + Allows the app to invoke methods to add and delete fingerprint templates for use. + + use fingerprint hardware + + Allows the app to use fingerprint hardware for authentication + read sync settings diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml index 352317da902d5..e19246cdd561c 100644 --- a/packages/Keyguard/AndroidManifest.xml +++ b/packages/Keyguard/AndroidManifest.xml @@ -40,6 +40,7 @@ + + diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 29415745c3b84..c44e39d65ff11 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -20,8 +20,11 @@ import android.app.Service; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.os.MessageQueue; import android.os.PowerManager; import android.os.RemoteException; import android.provider.Settings; @@ -34,6 +37,8 @@ import com.android.server.SystemService; import android.service.fingerprint.FingerprintUtils; import android.service.fingerprint.IFingerprintService; import android.service.fingerprint.IFingerprintServiceReceiver; +import static android.Manifest.permission.MANAGE_FINGERPRINT; +import static android.Manifest.permission.USE_FINGERPRINT; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -68,14 +73,13 @@ public class FingerprintService extends SystemService { } }; private Context mContext; + private int mHalDeviceId; 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_REMOVING = 3; private static final long MS_PER_SEC = 1000; - public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; - public static final String ENROLL_FINGERPRINT = "android.permission.ENROLL_FINGERPRINT"; private static final class ClientData { public IFingerprintServiceReceiver receiver; @@ -113,17 +117,17 @@ public class FingerprintService extends SystemService { public FingerprintService(Context context) { super(context); mContext = context; - nativeInit(this); + nativeInit(Looper.getMainLooper().getQueue(), this); } // TODO: Move these into separate process // JNI methods to communicate from FingerprintManagerService to HAL - native int nativeEnroll(int timeout); - native int nativeEnrollCancel(); - native int nativeRemove(int fingerprintId); - native int nativeOpenHal(); - native int nativeCloseHal(); - native void nativeInit(FingerprintService service); + static native int nativeEnroll(int timeout); + static native int nativeEnrollCancel(); + static native int nativeRemove(int fingerprintId); + static native int nativeOpenHal(); + static native int nativeCloseHal(); + static native void nativeInit(MessageQueue queue, FingerprintService service); // JNI methods for communicating from HAL to clients void notify(int msg, int arg1, int arg2) { @@ -131,11 +135,13 @@ public class FingerprintService extends SystemService { } void handleNotify(int msg, int arg1, int arg2) { - Slog.v(TAG, "handleNotify(msg=" + msg + ", arg1=" + arg1 + ", arg2=" + arg2 + ")"); + Slog.v(TAG, "handleNotify(msg=" + msg + ", arg1=" + arg1 + ", arg2=" + arg2 + ")" + + ", " + mClients.size() + " clients"); for (int i = 0; i < mClients.size(); i++) { + if (DEBUG) Slog.v(TAG, "Client[" + i + "] binder token: " + mClients.keyAt(i)); ClientData clientData = mClients.valueAt(i); if (clientData == null || clientData.receiver == null) { - if (DEBUG) Slog.v(TAG, "clientData at " + i + " is invalid!!"); + if (DEBUG) Slog.v(TAG, "clientData is invalid!!"); continue; } switch (msg) { @@ -282,26 +288,27 @@ public class FingerprintService extends SystemService { mClients.remove(token); } - void checkPermission(String permisison) { - // TODO + void checkPermission(String permission) { + getContext().enforceCallingOrSelfPermission(permission, "Must have " + + permission + " permission."); } private final class FingerprintServiceWrapper extends IFingerprintService.Stub { @Override // Binder call public void enroll(IBinder token, long timeout, int userId) { - checkPermission(ENROLL_FINGERPRINT); + checkPermission(MANAGE_FINGERPRINT); startEnroll(token, timeout, userId); } @Override // Binder call public void enrollCancel(IBinder token,int userId) { - checkPermission(ENROLL_FINGERPRINT); + checkPermission(MANAGE_FINGERPRINT); startEnrollCancel(token, userId); } @Override // Binder call public void remove(IBinder token, int fingerprintId, int userId) { - checkPermission(ENROLL_FINGERPRINT); // TODO: Maybe have another permission + checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission startRemove(token, fingerprintId, userId); } @@ -317,12 +324,19 @@ public class FingerprintService extends SystemService { checkPermission(USE_FINGERPRINT); removeListener(token, userId); } + + @Override // Binder call + public boolean isHardwareDetected() { + checkPermission(USE_FINGERPRINT); + return mHalDeviceId != 0; + } } @Override public void onStart() { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); - nativeOpenHal(); + mHalDeviceId = nativeOpenHal(); + if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId); } }