Fingerprint指纹识别学习
Fingerprint模块架构图如下,这里分为application,framework,fingerprintd和FingerprintHal这几个部分,不涉及指纹的IC库和驱动这部分,这部分逻辑由指纹厂商来实现,目前了解的并不多。
二、Fingerprint framework初始化流程
/*** Starts a miscellaneous grab bag of stuff that has yet to be refactored* and organized.*/private void startOtherServices() {final Context context = mSystemContext;VibratorService vibrator = null;IMountService mountService = null;.......//启动FingerprintServiceif (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {mSystemServiceManager.startService(FingerprintService.class);}......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这里会通过PackageManager来判断是否支持指纹功能,这个判断是N新加的,如果支持的话,需要在framework/native/data/ect/目录下添加Android.hardware.fingerprint.xml来支持该功能,这样才能启动FingerprintService。
这里启动的时候,会将FingerprintService添加到ServiceManager中去,如下图:
将FingerprintService添加到ServiceManager中后,在SystemServiceRegistry.java中静态代码块中注册服务的时候,可以从ServiceManager中获取FingerprintService的Binder对象,从而可以构造出FingerprintManager对象,这样app端就可以通过Context来获取FingerprintManager对象。
这样,app端通过Context获取FingerprintManager,通过调用FingerprintManager的接口来实现相应的功能,FingerprintManager转调FingerprintService中方法,FingerprintService负责管理整个注册,识别、删除指纹、检查权限等流程的逻辑,FingerprintService调用fingerprintd的接口,通过fingerprintd和FingerprintHal层进行通信。
在FingerprintService的getFingerprintDaemon方法中有如下步骤:
//①获取fingerprintd
//②向fingerprintd注册回调函数mDaemonCallback
//③调用获取fingerprintd的openhal函数
//④建立fingerprint文件系统节点,设置节点访问权限,调用fingerprintd的setActiveGroup,将路径传下去。此路径一半用来存储指纹模板的图片等
public IFingerprintDaemon getFingerprintDaemon() {if (mDaemon == null) {//①获取fingerprintdmDaemon = IFingerprintDaemon.Stub.asInterface(ServiceManager.getService(FINGERPRINTD));if (mDaemon != null) {try {mDaemon.asBinder().linkToDeath(this, 0);//②向fingerprintd注册回调函数mDaemonCallbackmDaemon.init(mDaemonCallback);//③调用获取fingerprintd的openhal函数mHalDeviceId = mDaemon.openHal();/*④建立fingerprint文件系统节点,设置节点访问权限,调用fingerprintd的setActiveGroup,将路径传下去。此路径一半用来存储指纹模板的图片等*/if (mHalDeviceId != 0) {updateActiveGroup(ActivityManager.getCurrentUser());} else {Slog.w(TAG, "Failed to open Fingerprint HAL!");mDaemon = null;}} catch (RemoteException e) {Slog.e(TAG, "Failed to open fingeprintd HAL", e);mDaemon = null; // try again later!}} else {Slog.w(TAG, "fingerprint service not available");}}return mDaemon;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
1 FingerprintService在framework模块负责指纹的大部分逻辑,FingerprintService会在开机的时候初始化;
2 application调用framework通过FingerprintManager接口即可实现;
3 framework中FingerManager和FingerprintService的通信使用Binder机制实现,表现即使用aidl这个接口定义语言实现
4 framework调往fingerprintd的同样属于Binder通信,两者分属于不同的进程。不过这部分跟java层Binder处理有点不一样,是java层往native层的调用。
三、fingerprintd 部分的初始化
在system/core/fingerprintd目录下,有如下文件:
fingerprintd如果划分的比较细的话,可以分为四个部分:
1.fingerprintd.cpp "负责将fingerprintd加入到ServiceManager中,以便FingerprintService能够获取"
2.IFingerprintDaemon.h/IFingerprintDaemon.cpp "负责java层到fingerprintd的Binder通信"
3.FingerprintDaemonProxy.h/FingerprintDaemonProxy.cpp "负责fingerprintd和Fignerprint hal层的通信"
4.IFingerprintDaemonCallback.h/IFingerprintDaemonCallback.cpp "负责将指纹的回调结果传给java层"
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
fingerprintd在init.rc有相应的开机启动脚本,所以一开机就会跑它的main函数。fingerprintd作为一个独立的进程运行,负责将Framework和Hal层的通信连接起来。
fingerprintd 的main函数就是将fingerprintd添加到servicemanager中管理。然后开了一个线程,等待binder消息。
四、接下来简单介绍下IFingerprintDaemon是如何跟framework通信的。
17#ifndef IFINGERPRINT_DAEMON_H_
18#define IFINGERPRINT_DAEMON_H_
19
20#include <binder/IInterface.h>
21#include <binder/Parcel.h>
22
23namespace android {
24
25class IFingerprintDaemonCallback;
26
27/*
28* Abstract base class for native implementation of FingerprintService.
29*
30* Note: This must be kept manually in sync with IFingerprintDaemon.aidl
31*/
32class IFingerprintDaemon : public IInterface, public IBinder::DeathRecipient {
33 public:
34 enum {
35 AUTHENTICATE = IBinder::FIRST_CALL_TRANSACTION + 0,
36 CANCEL_AUTHENTICATION = IBinder::FIRST_CALL_TRANSACTION + 1,
37 ENROLL = IBinder::FIRST_CALL_TRANSACTION + 2,
38 CANCEL_ENROLLMENT = IBinder::FIRST_CALL_TRANSACTION + 3,
39 PRE_ENROLL = IBinder::FIRST_CALL_TRANSACTION + 4,
40 REMOVE = IBinder::FIRST_CALL_TRANSACTION + 5,
41 GET_AUTHENTICATOR_ID = IBinder::FIRST_CALL_TRANSACTION + 6,
42 SET_ACTIVE_GROUP = IBinder::FIRST_CALL_TRANSACTION + 7,
43 OPEN_HAL = IBinder::FIRST_CALL_TRANSACTION + 8,
44 CLOSE_HAL = IBinder::FIRST_CALL_TRANSACTION + 9,
45 INIT = IBinder::FIRST_CALL_TRANSACTION + 10,
46 POST_ENROLL = IBinder::FIRST_CALL_TRANSACTION + 11,
47 ENUMERATE = IBinder::FIRST_CALL_TRANSACTION + 12,
48 };
49
50 IFingerprintDaemon() { }
51 virtual ~IFingerprintDaemon() { }
52 virtual const android::String16& getInterfaceDescriptor() const;
53
54 // Binder interface methods
55 virtual void init(const sp<IFingerprintDaemonCallback>& callback) = 0;
56 virtual int32_t enroll(const uint8_t* token, ssize_t tokenLength, int32_t groupId,
57 int32_t timeout) = 0;
58 virtual uint64_t preEnroll() = 0;
59 virtual int32_t postEnroll() = 0;
60 virtual int32_t stopEnrollment() = 0;
61 virtual int32_t authenticate(uint64_t sessionId, uint32_t groupId) = 0;
62 virtual int32_t stopAuthentication() = 0;
63 virtual int32_t remove(int32_t fingerId, int32_t groupId) = 0;
64 virtual int32_t enumerate() = 0;
65 virtual uint64_t getAuthenticatorId() = 0;
66 virtual int32_t setActiveGroup(int32_t groupId, const uint8_t* path, ssize_t pathLen) = 0;
67 virtual int64_t openHal() = 0;
68 virtual int32_t closeHal() = 0;
69
70 // DECLARE_META_INTERFACE - C++ client interface not needed
71 static const android::String16 descriptor;
72 static void hal_notify_callback(const fingerprint_msg_t *msg);
73};
74
75// ----------------------------------------------------------------------------
76
77class BnFingerprintDaemon: public BnInterface<IFingerprintDaemon> {
78 public:
79 virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
80 uint32_t flags = 0);
81 private:
82 bool checkPermission(const String16& permission);
83};
84
85} // namespace android
86
87#endif // IFINGERPRINT_DAEMON_H_
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
java层到fingerprintd的通信这里同样是采用binder方式,注意到上面IFingerprintDaemon.h第30行的NOTE,需要手动保证IFingerprintDaemon.h文件与IFingerprintDaemon.aidl文件一致,由于java层aidl文件编译时会自动编译成IFingerprintDaemon.java文件。
当添加接口来调用指纹底层暴露的接口,在IFingerprintDaemon.h文件中添加类似上面35行到68行的枚举,枚举的值需要与java层aidl自动生成的java文件中的枚举保持一致。另外还需要在上面68行处加上描述这些接口的纯虚函数(c++中的纯虚函数类似java的抽象方法,用于定义接口的规范,在C++中,一个具有纯虚函数的基类被称为抽象类)。
如下面截图对比,我们发现IFingerprintDaemon.cpp和java层aidl生成的IFingerprintDaemon.java在onTransact是基本一致的。这样我们也就明白了为什么上面说需要手动和IFingerprintDaemon.aidl保持同步了,这样方式类似我们平时在三方应用使用aidl文件,需要保持client端和server端aidl文件一致。
可以看到onTransact有四个参数
code , data ,replay , flags
code 是一个整形的唯一标识,用于区分执行哪个方法,客户端会传递此参数,告诉服务端执行哪个方法
data客户端传递过来的参数
replay服务器返回回去的值
flags标明是否有返回值,0为有(双向),1为没有(单向)
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
IFingerprintDaemon.aidl文件生成的IFingerprintDaemon.java文件
接着介绍下fingerprintd进程是如何和Fingerprint Hal层是如何传递数据的。
说到Hal层,即硬件抽象层,Android系统为HAL层中的模块接口定义了规范,所有工作于HAL的模块必须按照这个规范来编写模块接口,否则将无法正常访问硬件。
指纹的HAL层规范fingerprint.h在/hardware/libhardware/include/hardware/下可以看到。
我们注意到在fingerprint.h中定义了两个结构体,分别是fingerprint_device_t和fingerprint_module_t,如下图。
序文:如何调用Hal层库文件
每个Hal层库文件有一个入口,即HAL_MODULE_INFO_SYM,上层在调用hal层库文件时会在/system/lib/hw/下面寻找对应库文件,找到对应库文件后便从入口HAL_MODULE_INFO_SYM调用Hal层里面的open, init, write, read等接口,Hal层再通过这个接口去读写设备节点。
一、 fingerprint.default.so
//根据名称获取指纹hal层模块。hw_module这个一般由指纹芯片厂商根据 fingerprint.h实现
if (0 != (err = hw_get_module(FINGERPRINT_HARDWARE_MODULE_ID, &hw_module))) {ALOGE("Can't open fingerprint HW Module, error: %d", err);return 0;
}
我们继续往下看fingerprint.default.so。
static struct hw_module_methods_t fingerprint_module_methods = {
.open = fingerprint_open,
};fingerprint_module_t HAL_MODULE_INFO_SYM = {.common = {.tag = HARDWARE_MODULE_TAG,.module_api_version = FINGERPRINT_MODULE_API_VERSION_2_0,.hal_api_version = HARDWARE_HAL_API_VERSION,.id = FINGERPRINT_HARDWARE_MODULE_ID,.name = "Fingerprint HAL",.author = "xxx",.methods = &fingerprint_module_methods,.dso = NULL},
};
2、fingerprintd得到了相应的fingerprint_module_t,之后就会去调用它的open函数。我们来看一下初始化指纹最核心的fingerprint_open。
static int fingerprint_open(const hw_module_t* module, const char __unused *id,hw_device_t** device)
{ALOGV("fingerprint_open");if (device == NULL) {ALOGE("NULL device on open");return -EINVAL;}fingerprint_device_t *dev = (fingerprint_device_t *)malloc(sizeof(fingerprint_device_t));memset(dev, 0, sizeof(fingerprint_device_t));dev->common.tag = HARDWARE_DEVICE_TAG;dev->common.version = FINGERPRINT_MODULE_API_VERSION_2_0;dev->common.module = (struct hw_module_t*) module;dev->common.close = fingerprint_close;dev->pre_enroll = fingerprint_pre_enroll;dev->enroll = fingerprint_enroll;dev->post_enroll = fingerprint_post_enroll;dev->get_authenticator_id = fingerprint_get_auth_id;dev->cancel = fingerprint_cancel;dev->remove = fingerprint_remove;dev->set_active_group = fingerprint_set_active_group;dev->authenticate = fingerprint_authenticate;dev->set_notify = set_notify_callback;dev->notify = NULL;g_device = dev;if(g_device == NULL) {ALOGV("g_device is NULL");} else {ALOGV("g_device is not NULL");}*device = (hw_device_t*) dev;hal_init(mDevice);return 0;
}
二、重要的hal_init函数。
1、 hal_device_open()的工作很简单,就是打开指纹驱动层的设备节点,然后初始化一个用来接收驱动层消息的消息队列。当然在此之前,指纹的驱动层肯定已经正常probe,生成了相应的设备节点。
fd = open(/dev/xxx_fp, O_RDWR);
...
TAILQ_INIT(&head);
...
err = hal_get_fw_info(&download_fw_flag);if (err != GF_SUCCESS) {LOG_E(LOG_TAG "[%s] failed to get firmware info", __func__);}if (!download_fw_flag) {hal_reset_chip();}
3、与指纹ta建立session,然后调用接口初始化指纹ta。
result = TEEC_OpenSession(g_context, g_session,&UUID, TEEC_LOGIN_PUBLIC, NULL, &operation, NULL);
...TEEC_Operation operation = { 0 };
operation.paramTypes = GF_CMD_TEEC_PARAM_TYPES;
operation.params[0].tmpref.buffer = GF_CMD_INIT;
operation.params[0].tmpref.size = len;
ret = TEEC_InvokeCommand(g_session, GF_OPERATION_ID, &operation, NULL);
...
4、与指纹驱动层建立通信。这里给大家看一种基于netlink,巧妙而简洁的方式。
4.1.1、通信的接收端(hal层)做了哪些处理?我们往下看
//初始化信号量 g_sem,配合消息队列,用于从消息接受者hal_netlink_recv
//到消息处理者handle_thread的消息传递
if (0 != sem_init(&g_sem, 0, 0)) {LOG_E(LOG_TAG, "[%s] init semaphore failed", __func__);break;
}//消息处理线程handle_thread
if (pthread_create(&g_handle_thread, NULL, handle_thread, NULL) != 0) {LOG_E(LOG_TAG, "[%s] pthread_create failed", __func__);break;
}
//用ioctl的方式将netlink描述符g_netlink_route传递给驱动层。
//这样驱动层就能用这个g_netlink_route与hal层建立消息管道
if (ioctl(fd, GF_IOC_INIT, &g_netlink_route) != 0) {LOG_E(LOG_TAG, "[%s] GF_IOC_INIT ioctl failed", __func__);err = GF_ERROR_OPEN_DEVICE_FAILED;break;
}
LOG_I(LOG_TAG, "[%s] g_netlink_route = %d", __func__, g_netlink_route);//消息接收线程hal_netlink_recv
if (pthread_create(&g_netlink_thread, NULL, hal_netlink_recv, NULL) != 0) {LOG_E(LOG_TAG, "[%s] pthread_create failed", __func__);break;
}
4.1.2、我们先看消息接收线程hal_netlink_recv做了什么。
/* 初始化netlink并binder 下面这些都是netlink的标准流程*/g_netlink_sock_id = socket(AF_NETLINK, SOCK_RAW, g_netlink_route);if (g_netlink_sock_id < 0) {break;}memset(&local, 0, sizeof(struct sockaddr_nl));local.nl_family = AF_NETLINK;local.nl_pid = getpid();/*local process id*/local.nl_groups = 0;ret = bind(g_netlink_sock_id, (struct sockaddr*) &local, sizeof(struct sockaddr_nl));if (ret != 0) {break;}/* send init message */memset(&dest, 0, sizeof(struct sockaddr_nl));dest.nl_family = AF_NETLINK;dest.nl_pid = 0; /*destination is kernel so set to 0*/dest.nl_groups = 0;nlh = (struct nlmsghdr *) malloc(NLMSG_SPACE(MAX_NL_MSG_LEN));if (NULL == nlh) {LOG_E(LOG_TAG, "[%s] nlh out of memery", __func__);break;}nlh->nlmsg_len = NLMSG_SPACE(MAX_NL_MSG_LEN);nlh->nlmsg_pid = getpid();nlh->nlmsg_flags = 0;strcpy(NLMSG_DATA(nlh), "GF");iov.iov_base = (void*) nlh;iov.iov_len = nlh->nlmsg_len;memset(&msg, 0, sizeof(struct msghdr));msg.msg_iov = &iov;msg.msg_iovlen = 1;msg.msg_name = (void*) &dest;msg.msg_namelen = sizeof(struct sockaddr_nl);//发送一个包含pid的消息给驱动层,相当于握手,告诉驱动层,我这边已经准备ok了。if (sendmsg(g_netlink_sock_id, &msg, 0) < 0) {break;}LOG_D(LOG_TAG, "[%s] send init msg to kernel", __func__);/* 开启一个循环,接收来自驱动层的消息 */memset(nlh, 0, NLMSG_SPACE(MAX_NL_MSG_LEN));while (1) {//LOG_D(LOG_TAG, "here wait message from kernel");ret = recvmsg(g_netlink_sock_id, &msg, 0);if (ret < 0) {LOG_E(LOG_TAG, "[%s] recvmsg failed, ret %d", __func__, ret);continue;}if (0 == ret) {LOG_E(LOG_TAG, "[%s] recvmsg failed, ret %d", __func__, ret);continue;}value = *((char *) NLMSG_DATA(nlh));//根据消息类别做处理if (GF_NETLINK_TEST == value) {LOG_D(LOG_TAG, "[%s] received GF_NETLINK_TEST command", __func__);} else if (NETLINK_IRQ == value || NETLINK_SCREEN_OFF == value|| NETLINK_SCREEN_ON == value) {//如果是中断消息,或者亮灭屏事件,就把消息值push到消息队列。//然后post信号量,让消息处理线程去处理了。enqueue(value);sem_post(&g_netlink_sem);LOG_D(LOG_TAG, "[%s] send message : %d", __func__, value);} else {LOG_E(LOG_TAG, "[%s] wrong netlink command %d", __func__, value);}}
4.1.3、再看处理线程,等待信号量,收到之后就从消息队列里边取出消息。然后根据不同的值调用相应的处理函数。
void *handle_thread(void *handle) {while (1) {sem_wait(&g_netlink_sem);err = dequeue(&value);if (err != GF_SUCCESS) {continue;}if (GF_NETLINK_IRQ == value) {hal_irq();} else if (GF_NETLINK_SCREEN_OFF == value) {hal_screen_off();} else if (GF_NETLINK_SCREEN_ON == value) {hal_screen_on();}}
}
hal层的设计很清晰。由于中断来的很快,频率也很高,所以这边使用快速接收中断,缓存起来,再慢慢处理的方式处理中断事件,类似于内核中断上下文的处理方式。
4.2.1、讲到这里,肯定对驱动层怎么发送接收消息产生了好奇?本来打算在写驱动层的地方讲的,但是这样这部分内容就中断了,还是现在这里写完吧。很简单,直接看下面的代码注释就能理解。
static int netlink_init(void)
{struct netlink_kernel_cfg cfg;memset(&cfg, 0, sizeof(struct netlink_kernel_cfg));cfg.input = netlink_recv;//创建netlink 驱动层的接收hal层消息函数,注意NETLINK_ROUTE要与hal层一致。g_dev->nl_sk = netlink_kernel_create(&init_net, NETLINK_ROUTE, &cfg);
}
static void netlink_recv(struct sk_buff *__skb)
{skb = skb_get(__skb);//消息大于5byte才做处理if (skb->len >= NLMSG_SPACE(0)) {nlh = nlmsg_hdr(skb);memcpy(str, NLMSG_DATA(nlh), sizeof(str));//拿到了hal层穿下来的pid,保存起来。g_gf_dev->pid = nlh->nlmsg_pid;} else {debug(ERR_LOG, "[%s] : not enough data length\n", __func__);}kfree_skb(skb);
}
4.2.3、收到中断或者亮灭屏事件,就调用netlink_send通知hal层:
void netlink_send(const int command)
{//netlink kernel层发送消息的典型流程,就是构造一个消息结构体,然后//用api netlink_unicast发出去skb = alloc_skb(MAX_NL_MSG_LEN, GFP_ATOMIC);if (skb == NULL) {gf_debug(ERR_LOG, "[%s] : allocate skb failed\n", __func__);return;}nlh = nlmsg_put(skb, 0, 0, 0, MAX_NL_MSG_LEN, 0);if (!nlh) {kfree_skb(skb);return;}NETLINK_CB(skb).portid = 0;NETLINK_CB(skb).dst_group = 0;//消息类型的赋值,中断,亮灭屏等*(char *)NLMSG_DATA(nlh) = command;ret = netlink_unicast(g_gf_dev->nl_sk, skb, g_gf_dev->pid, MSG_DONTWAIT);
}
这样,hal层和驱动层就建立好了通信管道。以后中断等事件就能从驱动层报给hal层,hal层会根据事件类型,做相应处理。
至此,hal层就算初始化完毕了。接下来,上层就可以开始注册指纹了。
下面实例::::::::::
笔者手中的设备就是图上的那台黑色的nexus 5x,话说这台机器很是好看呢!手感超棒!
废话不多说,下面我出一个指纹识别的demo app,并且详细说明怎么开发一个基于google api的指纹识别app。demo的源码在我的github上:
https://github.com/CreateChance/AndroidFingerPrintDemo
Android M中的指纹识别接口
动手开发一个指纹识别app
现在,我们要动手写一个利用上面接口的指纹识别app,这个app界面很简单,就一个activity,这个activity上会激活指纹识别,然后提示用户按下指纹,并且会将认证的结果显示出来。
开始
在开始之前,我们需要知道使用指纹识别硬件的基本步骤:
1.在AndroidManifest.xml中申明如下权限:
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
- 1
- 1
2.获得FingerprintManager的对象引用
3.在运行是检查设备指纹识别的兼容性,比如是否有指纹识别设备等。下面我们详细说一下上面的步骤:
申明权限
这一步比较简单,只要在AndroidManifest.xml中添加上面说到的权限就可以了。
获得FingerprintManager对象引用
// Using the Android Support Library v4
fingerprintManager = FingerprintManagerCompat.from(this);
// Using API level 23:
fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
上面给出两种方式,第一种是通过V4支持包获得兼容的对象引用,这是google推行的做法;还有就是直接使用api 23 framework中的接口获得对象引用。
检查运行条件
if (!fingerprintManager.isHardwareDetected()) {// no fingerprint sensor is detected, show dialog to tell user.AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setTitle(R.string.no_sensor_dialog_title);builder.setMessage(R.string.no_sensor_dialog_message);builder.setIcon(android.R.drawable.stat_sys_warning);builder.setCancelable(false);builder.setNegativeButton(R.string.cancel_btn_dialog, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {finish();}});// show this dialog.builder.create().show();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
调用上面的接口接可以知道系统中是不是有一个这样的硬件,如果没有的话,那就需要做一些合适的事情,比如提示用户当前系统中没有指纹识别硬件等。
3). 当前设备必须是处于安全保护中的
这个条件的意思是,你的设备必须是使用屏幕锁保护的,这个屏幕锁可以是password,PIN或者图案都行。为什么是这样呢?因为google原生的逻辑就是:想要使用指纹识别的话,必须首先使能屏幕锁才行,这个和android 5.0中的smart lock逻辑是一样的,这是因为google认为目前的指纹识别技术还是有不足之处,安全性还是不能和传统的方式比较的。
我们可以使用下面的代码检查当前设备是不是处于安全保护中的:
KeyguardManager keyguardManager =(KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager.isKeyguardSecure()) {// this device is secure.
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
我们使用KeyguardManager的isKeyguardSecure接口就能知道。
4). 系统中是不是有注册的指纹
在android 6.0中,普通app要想使用指纹识别功能的话,用户必须首先在setting中注册至少一个指纹才行,否则是不能使用的。所以这里我们需要检查当前系统中是不是已经有注册的指纹信息了:
if (!fingerprintManager.hasEnrolledFingerprints()) {// no fingerprint image has been enrolled.AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setTitle(R.string.no_fingerprint_enrolled_dialog_title);builder.setMessage(R.string.no_fingerprint_enrolled_dialog_message);builder.setIcon(android.R.drawable.stat_sys_warning);builder.setCancelable(false);builder.setNegativeButton(R.string.cancel_btn_dialog, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {finish();}});// show this dialogbuilder.create().show();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
如果用户还没有注册一个指纹的话,那么我们的app可以提示用户:如果想要使用指纹是功能,请再setting中注册一个你的指纹。这里需要啰嗦一句,如果你做过bluetooth或者其他设备开发的话,那么你知道你可以通过发送一个intent来启动bluetooth开启的界面,只要是声明了蓝牙的管理权限。但是,到目前位置google任然没有开放让普通app启动指纹注册界面的权限,这一点我们可以从setting的AndroidManifest中看到:
<activity android:name=".fingerprint.FingerprintSettings" android:exported="false"/>
<activity android:name=".fingerprint.FingerprintEnrollOnboard" android:exported="false"/>
<activity android:name=".fingerprint.FingerprintEnrollFindSensor" android:exported="false"/>
<activity android:name=".fingerprint.FingerprintEnrollEnrolling" android:exported="false"/>
<activity android:name=".fingerprint.FingerprintEnrollFinish" android:exported="false"/>
<activity android:name=".fingerprint.FingerprintEnrollIntroduction" android:exported="false" /><activity android:name=".fingerprint.SetupFingerprintEnrollOnboard" android:exported="false"/>
<activity android:name=".fingerprint.SetupFingerprintEnrollFindSensor" android:exported="false"/>
<activity android:name=".fingerprint.SetupFingerprintEnrollEnrolling" android:exported="false"/>
<activity android:name=".fingerprint.SetupFingerprintEnrollFinish" android:exported="false"/>
<activity android:name=".fingerprint.SetupFingerprintEnrollIntroduction"android:exported="true"android:permission="android.permission.MANAGE_FINGERPRINT"android:theme="@style/SetupWizardDisableAppStartingTheme"><intent-filter><action android:name="android.settings.FINGERPRINT_SETUP" /><category android:name="android.intent.category.DEFAULT" /></intent-filter>
</activity>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
大部分的fingerprint设置界面都没有exporte,只有SetupFingerprintEnrollIntroduction,但是这个界面需要android.permission.MANAGE_FINGERPRINT这个权限,并且这个权限只能是系统app使用,这就直接防止第三方app启动这个界面了。(不知道日后google会不会开放这个权限。。。。。)
一个好的app,应该在运行时都检查一下上面的条件,防止app出现意外的错误。
扫描用户按下的指纹
取消指纹扫描
创建CryptoObject类对象
public class CryptoObjectHelper
{// This can be key name you want. Should be unique for the app.static final String KEY_NAME = "com.createchance.android.sample.fingerprint_authentication_key";// We always use this keystore on Android.static final String KEYSTORE_NAME = "AndroidKeyStore";// Should be no need to change these values.static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;static final String TRANSFORMATION = KEY_ALGORITHM + "/" +BLOCK_MODE + "/" +ENCRYPTION_PADDING;final KeyStore _keystore;public CryptoObjectHelper() throws Exception{_keystore = KeyStore.getInstance(KEYSTORE_NAME);_keystore.load(null);}public FingerprintManagerCompat.CryptoObject buildCryptoObject() throws Exception{Cipher cipher = createCipher(true);return new FingerprintManagerCompat.CryptoObject(cipher);}Cipher createCipher(boolean retry) throws Exception{Key key = GetKey();Cipher cipher = Cipher.getInstance(TRANSFORMATION);try{cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key);} catch(KeyPermanentlyInvalidatedException e){_keystore.deleteEntry(KEY_NAME);if(retry){createCipher(false);} else{throw new Exception("Could not create the cipher for fingerprint authentication.", e);}}return cipher;}Key GetKey() throws Exception{Key secretKey;if(!_keystore.isKeyEntry(KEY_NAME)){CreateKey();}secretKey = _keystore.getKey(KEY_NAME, null);return secretKey;}void CreateKey() throws Exception{KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME);KeyGenParameterSpec keyGenSpec =new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).setBlockModes(BLOCK_MODE).setEncryptionPaddings(ENCRYPTION_PADDING).setUserAuthenticationRequired(true).build();keyGen.init(keyGenSpec);keyGen.generateKey();}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
上面的类会针对每个CryptoObject对象都会新建一个Cipher对象,并且会使用由应用生成的key。这个key的名字是使用KEY_NAME变量定义的,这个名字应该是保证唯一的,建议使用域名区别。GetKey方法会尝试使用Android Keystore的API来解析一个key(名字就是上面我们定义的),如果key不存在的话,那就调用CreateKey方法新建一个key。
cipher变量的实例化是通过调用Cipher.getInstance方法获得的,这个方法接受一个transformation参数,这个参数制定了数据怎么加密和解密。然后调用Cipher.init方法就会使用应用的key来完成cipher对象的实例化工作。
这里需要强调一点,在以下情况下,android会认为当前key是无效的:
1. 一个新的指纹image已经注册到系统中
2. 当前设备中的曾经注册过的指纹现在不存在了,可能是被全部删除了
3. 用户关闭了屏幕锁功能
4. 用户改变了屏幕锁的方式
当上面的情况发生的时候,Cipher.init方法都会抛出KeyPermanentlyInvalidatedException的异常,上面我的代码中捕获了这个异常,并且删除了当前无效的key,然后根据参数尝试再次创建。
上面的代码中使用了android的KeyGenerator来创建一个key并且把它存储在设备中。KeyGenerator类会创建一个key,但是需要一些原始数据才能创建key,这些原始的信息是通过KeyGenParameterSpec类的对象来提供的。KeyGenerator类对象的实例化是使用它的工厂方法getInstance进行的,从上面的代码中我们可以看到这里使用的AES(Advanced Encryption Standard )加密算法的,AES会将数据分成几个组,然后针对几个组进行加密。
接下来,KeyGenParameterSpec的实例化是使用它的Builder方法,KeyGenParameterSpec.Builder封装了以下重要的信息:
1. key的名字
2. key必须在加密和解密的时候是有效的
3. 上面代码中BLOCK_MODE被设置为Cipher Block Chaining也就是KeyProperties.BLOCK_MODE_CBC,这意味着每一个被AES切分的数据块都与之前的数据块进行了异或运算了,这样的目的就是为了建立每个数据块之间的依赖关系。
4. CryptoObjectHelper类使用了PKSC7(Public Key Cryptography Standard #7)的方式去产生用于填充AES数据块的字节,这样就是要保证每个数据块的大小是等同的(因为需要异或计算还有方面算法进行数据处理,详细可以查看AES的算法原理)。
5. setUserAuthenticationRequired(true)调用意味着在使用key之前用户的身份需要被认证。
每次KeyGenParameterSpec创建的时候,他都被用来初始化KeyGenerator,这个对象会产生存储在设备上的key。
怎么使用CryptoObjectHelper呢?
下面我们看一下怎么使用CryptoObjectHelper这个类,我们直接看代码就知道了:
CryptoObjectHelper cryptoObjectHelper = new CryptoObjectHelper();
fingerprintManager.authenticate(cryptoObjectHelper.buildCryptoObject(), 0,cancellationSignal, myAuthCallback, null);
- 1
- 2
- 3
- 1
- 2
- 3
使用是比较简单的,首先new一个CryptoObjectHelper对象,然后调用buildCryptoObject方法就能得到CryptoObject对象了。
处理用户的指纹认证结果
package com.createchance.fingerprintdemo;import android.os.Handler;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;/*** Created by baniel on 7/21/16.*/
public class MyAuthCallback extends FingerprintManagerCompat.AuthenticationCallback {private Handler handler = null;public MyAuthCallback(Handler handler) {super();this.handler = handler;}@Overridepublic void onAuthenticationError(int errMsgId, CharSequence errString) {super.onAuthenticationError(errMsgId, errString);if (handler != null) {handler.obtainMessage(MainActivity.MSG_AUTH_ERROR, errMsgId, 0).sendToTarget();}}@Overridepublic void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {super.onAuthenticationHelp(helpMsgId, helpString);if (handler != null) {handler.obtainMessage(MainActivity.MSG_AUTH_HELP, helpMsgId, 0).sendToTarget();}}@Overridepublic void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {super.onAuthenticationSucceeded(result);try {result.getCryptoObject().getCipher().doFinal();if (handler != null) {handler.obtainMessage(MainActivity.MSG_AUTH_SUCCESS).sendToTarget();}} catch (IllegalBlockSizeException e) {e.printStackTrace();} catch (BadPaddingException e) {e.printStackTrace();}}@Overridepublic void onAuthenticationFailed() {super.onAuthenticationFailed();if (handler != null) {handler.obtainMessage(MainActivity.MSG_AUTH_FAILED).sendToTarget();}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
这个子类实现很简单,主要的实现方式就是将消息抛给主界面的Handler来处理:
handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.d(TAG, "msg: " + msg.what + " ,arg1: " + msg.arg1);switch (msg.what) {case MSG_AUTH_SUCCESS:setResultInfo(R.string.fingerprint_success);mCancelBtn.setEnabled(false);mStartBtn.setEnabled(true);cancellationSignal = null;break;case MSG_AUTH_FAILED:setResultInfo(R.string.fingerprint_not_recognized);mCancelBtn.setEnabled(false);mStartBtn.setEnabled(true);cancellationSignal = null;break;case MSG_AUTH_ERROR:handleErrorCode(msg.arg1);break;case MSG_AUTH_HELP:handleHelpCode(msg.arg1);break;}}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
这里分别处理四中回调,并且针对错误码调用handleErrorCode方法处理:
private void handleErrorCode(int code) {switch (code) {case FingerprintManager.FINGERPRINT_ERROR_CANCELED:setResultInfo(R.string.ErrorCanceled_warning);break;case FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE:setResultInfo(R.string.ErrorHwUnavailable_warning);break;case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT:setResultInfo(R.string.ErrorLockout_warning);break;case FingerprintManager.FINGERPRINT_ERROR_NO_SPACE:setResultInfo(R.string.ErrorNoSpace_warning);break;case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT:setResultInfo(R.string.ErrorTimeout_warning);break;case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS:setResultInfo(R.string.ErrorUnableToProcess_warning);break;}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
很简单,就是针对不同的错误码,设置界面上不同的显示文字,以提示用户。这里大家可以很据自己的需要修改逻辑。
针对帮助码调用handleHelpCode方法处理:
private void handleHelpCode(int code) {switch (code) {case FingerprintManager.FINGERPRINT_ACQUIRED_GOOD:setResultInfo(R.string.AcquiredGood_warning);break;case FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY:setResultInfo(R.string.AcquiredImageDirty_warning);break;case FingerprintManager.FINGERPRINT_ACQUIRED_INSUFFICIENT:setResultInfo(R.string.AcquiredInsufficient_warning);break;case FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL:setResultInfo(R.string.AcquiredPartial_warning);break;case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST:setResultInfo(R.string.AcquiredTooFast_warning);break;case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_SLOW:setResultInfo(R.string.AcquiredToSlow_warning);break;}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
这里的处理和handleErrorCode是一样的。
总结
demo运行效果截图(运行于nexus 5x)
Fingerprint指纹识别学习相关推荐
- 高通qcom sdm450/msm8953平台 fingerprint指纹模组移植
1:查看主板原理图获取硬件信息 系统硬件平台是sdm450/msm8953 在主板原理图上可以找到指纹相关的原理图部分如下: 该IC使用两路电, LDO6 1.8v, LDO10 2.8v. SPI通 ...
- 高通平台fingerprint指纹框架
指纹是android系统中目前应用比较广发的一种安全验证手段,它使得我们的手机安全得到了极大的提高,同时指纹它也拥有了极高权限,这就意味着,对于指纹这个软件需要一个绝对安全的运行环境,让外界很难突破去 ...
- .NET(C#、VB)APP开发——Smobiler平台控件介绍:FingerPrint指纹识别组件
本文简述如何在Smobiler中使用FingerPrint. Step 1. 新建一个SmobilerForm窗体,并在窗体中加入FingerPrint和Button,布局如下 Button的点击事件 ...
- 指纹识别学习(一):历史及疑问
将指纹应用于身份验证可以追溯到千年以前,犯人的画押,地契,凭据均实行签字画押的形式,这就让我想到指纹识别对的发展,那么古人是怎么进行指纹识别和笔迹鉴别的? 一.指纹识别可能应用的方法 1. 古代的手 ...
- Android fingerprint指纹/face人脸代码导读
快速链接: .
- Thinkpad T470p 在 Ubuntu 20.04 启用指纹识别
Thinkpad T470p 在 Ubuntu 20.04 启用指纹识别 # 用空格选中fingerprint指纹识别选项,enter保存更改 sudo pam-auth-update sudo ap ...
- MTK6771 Android8.0 REE 指纹代码移植
环境搭建 1,安装Ubuntu 16.04 LTS 版本,按照MT6771_Basic_V1.0.pdf 安装Android O 版本所需的基本编译工具. $ sudo apt-get install ...
- Thinkpad 设置指纹启动
Thinkpad 设置指纹启动(代替 BIOS 开机密码)按以下步骤进行: 前提:已安装 Lenovo Fingerprint 指纹软件. 1.BIOS 设置: 进入 Security -> F ...
- 《ThinkPad-设置指纹启动》
系统Windows 10 以联想ThinkPad T480 为例 驱动下载 Thinkpad 设置指纹启动(代替 BIOS 开机密码)按以下步骤进行: 前提:已安装 Lenovo Fingerprin ...
- Android随笔-指纹识别
Android指纹识别在Android 6.0以下是不支持的,Android 6.0使用FingerprintManager进行指纹识别,Android 9.0建议使用BiometricPrompt进 ...
最新文章
- JavaScript中判断为整数的多种方式
- Review meeting还开不开?
- 测试php是否连接mysql_如何测试php是否连接mysql成功
- maven mysql的jdbctemplate_JDBC、JDBCTemplate、MyBatis、Hiberante 比较与分析
- CSS浏览器兼容性----Hack
- php 位运算 负数,php的位运算详解
- 同网关劫持与不同网关劫持实例
- bash快捷键(防忘)
- Node.js TLSWrap 实现中的释放后使用漏洞分析
- 读Java面向对象编程(孙卫琴)
- UE4 使用蓝图进行编辑器扩展
- 虚拟仿真实验平台服务器需求,虚拟仿真实验中心平台建设方案.pptx
- 【机器学习基础】IV和WOE值
- oracle ^]字符,oracle 字符串操作
- java 拼图_拼图延迟的历史-Oracle正式将Java 9的发布日期推迟到2017年
- 【PHPWord】如何解决PHPWord的输出checkbox复选框并设置checked已勾选
- K-Fold交叉验证 原理+python实现
- 图像识别平台建设之路(自建+三方产品)
- 该网站未根据工信部相关法律规则进行备案
- 爬取猫眼电影top100
热门文章
- 什么是DNS Spoofing, DNS Hijacking, and DNS Cache Poisoning?
- 中国农业银行研发中心面试经历分享
- 使用 JDBC 连接不同版本 DB2 数据库的兼容性问题
- 云服务三兄弟:公有云、私有云和混合云有何不同
- 求出数组最大值的方法
- ubuntu中screen的使用
- html js 邮箱格式,email 格式_JavaScript验证Email邮箱格式的三种方法
- 计算机网络电子邮件的格式,电子邮件的格式是什么
- MYSQL临时表,大数据查询优化
- 网站机房:DDOS防火墙在企业机房中的应用介绍