Android输入系统解析及Native层模拟按键方案
Android输入系统解析及Native层模拟按键方案
目录
1 Android输入系统
1.1 Android Input核心交互
1.1.1 文件节点读写
1.1.2 Framework层抓取事件
1.1.2 节点传输协议
1.2 模拟按键方式
1.2.1 Java层方案
1.2.2 C层方案
2 Amlogic模拟按键实践
2.1基于鼠标的本地模拟
2.2 Framework层实践案列简述
1 Android输入系统
Android输入系统涉及到驱动,HAL|C 及Framework层,这里聚焦于驱动与本地执行层间的交互。其架构参考如下
1.1 Android Input核心交互
如上图,本篇分析聚焦于Native层与Kernel交互,帮助梳理c/c++层模拟按键技术支持。这里分为两个部分,文件节点读写及节点传输协议解析
1.1.1 文件节点读写
这里来分析下Android系统针对输入设备的读写逻辑,先来看下驱动写输入事件到文件节点
定位文件到drivers/input/input.c,这是Android input子系统核心实现
subsys_initcall(input_init);module_exit(input_exit);
这里可以看到,input是内核加载即加载的核心驱动,下面来看下其引导函数
很明显,这里主要做了几件事,在sys文件系统及proc文件系统建立对应目录,申请字符设备号等。这块涉及到input子系统初始化,不展开。这里关注驱动写事件到文件节点(文件节点在对应的设备注册时创建)。
一般的设备中断时,从其寄存器读取事件通过input系统input_report_abs函数上传,如下
其在input.h中以inline实现,下面来看下input_event函数
这里通过自旋锁禁用本地中断,完成一次中断处理(此时处于中断上下文),在调用input_handle_event数据前,通过is_event_supported来查看硬件设备支持的事件类型,在adb中可以通过命令getevent –p查看,编者的amlogic平台如下:
下面来看input_handle_event函数
这里首先获取对此事件的处理策略,再处理,这里简单来看下此函数
可以看到对tpye=ev_syn的事件,其处理flag为INPUT_PASS_TO_HANDLERS | INPUT_FLUSH,同理查看ev_rel的处理flag为INPUT_PASS_TO_HANDLERS
此时回到input_handle_event函数,来分析
这里有个重点,就是普通的事件上报,在第一步完成后,面临两个策略:
1 如果flag中有inpu_flush,则完成此事件上报
2 如果flag中午inpu_flush,但vals数组个数如果是max-1,则也需要分段上报。这里假设满足上面中的任何一条件,继续来看
这里首先通过rcu_read_lock(),进入读端临界区读取变量,之后通过input_to_handler来处理事件。这里先不关注重复事件处理,接着来看input_to_handler
这个函数,整体逻辑就是先filter事件,对于有效事件再通过handle来传递。
这里需要注意下,输入事件过滤的连续性,否则for循环处理逻辑就有问题
这里调用handler->events函数处理,这里需要说明下,此函数指针注册在evdec.c中,这里简单提一下,可深入如下:
接上文,这里来分析evdev-event函数,其内部又调用evdev-events函数,展开来看
这里可以看到数据通过数组指针传递到evdev子系统,待其处理,接下来看下evdev是如何处理这一组输入事件的
如注释,这里主要分为两步,即是组装事件到evdev的buffer中,第二部通知唤醒
先来看下如何组装批量数据
这里主要是把数据放入client->buffer。(kill_fasync异步通知机制这里暂不涉及)
接上文在组装完事件数据后,通过wake_up_interruptible通知唤醒到注册在等待队列上的进程。该函数不能直接的立即唤醒进程,而是由调度程序转换上下文,调整为可运行状态。在这个语境下次进程就是当前进程,由于内核写文件节点逻辑处于中断上下文,猜测应该在进程上下文进程因为一些条件阻塞在此等待队列中,此时就被唤醒了,来处理evdev_client中的buffer数组了。简单参考如下(evdev_read函数):
1.1.2 Framework层抓取事件
下个阶段来简单分析下输入事件的读取,基于input系统框架图,完成事件收发的是EventHub对象,这里来看下其构造方法
这里,device_path为/dev/input,此时将监控此目录的inotify fd加入到epoll中。
在INPUT-READER系统中通过EventHub.getevents()来获取事件,这个函数比较复杂,这里分步来描述下,怎么读取事件的,这涉及到后期的模拟按键
第一步:
这里声明了event 及 capacity,为后期循环读取事件的标杆。
第二步:
判断,是否有需要打开设备,如果有,直接跳出循环进行对应逻辑,可以看到此处理优先级比较高
第三步:
这里可以看到在一次循环中优先处理设备打开及关闭,同步的构造event事件。
到这一步才开始处理正式的设备事件,判断条件依赖mPendingEventIndex < mPendingEventCount,此循环通过这个来处理事件处理。
此时判断,buffer缓存是否用完,若用完,则直接完成此一轮循环,直到从kernel上报那一批数据都处理完,这个是核心点。
第四步:
如果经过前面三步处理,还没有跳出循环,说明已经没有了设备插拔事件,以及没有正式事件上报事件。此时开始进入epoll_wait函数显示阻塞函数,以等待kernel或者模拟端事件来临,开启下一批事件上报。
以上四步,目前来看有几点需要重点注意:
1 提取数据通过一个大循环来处理底层同一批次事件,类似一个水磊,循环不觉。
2 同一批次数据如果处理不完,需要经过几个循环处理,处理完才可以处理下一批次事件。
3 同一批次数据,优先处理设备插拔。
至此在Native层就完成了设备节点数据读取,其数据会通过管道过滤模式,最终到达App端。
1.1.2 节点传输协议
上一节简述了文件节点读写,重点聚焦在数据的传递,但是输入系统传递的数据格式是如何的,如何支持input业务,这个就涉及到input系统的节点传输协议。
在正式开始前,这里先基于编者amalogic平台,来实时看下输入事件
第一列代表设备节点,后面三列明显是上报数据,具体格式,具体可以参考下input.h中关于input_event的定义
struct input_event {struct timeval time;__u16 type;__u16 code;__s32 value;};
任何输入事件,最终都通过此结构体来上报信息,怎么来区分大批量数据代表什么输入事件内容?这个就涉及到文件节点传输协议,具体查案input.h定义就可以了。这里仅仅对type做下注释
types对应于一个相同逻辑输入结构的一组Codes。每个type都有一组可用的codes用于产生输入事件。每个type可用的codes的详细信息请参考Codes一节的内容。
* EV_SYN: - 用于事件间的分割标志。事件可能按时间或空间进行分割,就像在多点触摸协议中的例子。 * EV_KEY: - 用来描述键盘,按键或者类似键盘设备的状态变化。 * EV_REL: - 用来描述相对坐标轴上数值的变化,例如:鼠标向左方移动了5个单位。 * EV_ABS: -用来描述相对坐标轴上数值的变化,例如:描述触摸屏上坐标的值。 * EV_MSC: - 当不能匹配现有的类型时,使用该类型进行描述。 * EV_SW: - 用来描述具备两种状态的输入开关。 * EV_LED: - 用于控制设备上的LED灯的开和关。 * EV_SND: - 用来给设备输出提示声音。 * EV_REP: -用于可以自动重复的设备(autorepeating)。 * EV_FF: - 用来给输入设备发送强制回馈命令。(震动?) * EV_PWR: - 特别用于电源开关的输入。. * EV_FF_STATUS: - 用于接收设备的强制反馈状态。
关于code,有兴趣可自行查看input.h文件。Android input系统在数据上报时,通过tpye |code |value组合就可以完成全部信息的整合上报了。这里的文件节点协议对于后面c端模拟实现由重要的参考意义。
1.2 模拟按键方式
关于Android端模拟按键实现,其实在熟悉了input框架后,就可以有多种方案,一般来讲分为java端方案及本地Native方案
1.2.1 Java层方案
类比adb input keyevent value+ 这条命令,可以在源代码找到参考代码,如下,定位到源代码中frameworks/base/cmds/input/src/com/android/commands/input/Input
文件,其核心代码是
private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) { long now = SystemClock.uptimeMillis(); injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource)); if (longpress) { injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS, inputSource)); } injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));}
其核心是调用了injectKeyEvent方法:
可以看到,这里直接调用了IMS的方法,这样子实现起来就比较简单了。
1.2.2 C层方案
参考1.1节描述,可以通过直接在文件节点写入模拟按键事件即可,EventHub在检测到事件后会处理对应的事件,下文基于amalogic来实现native层模拟按键
2 Amlogic模拟按键实践
2.1基于鼠标的本地模拟
基于第二节分析,在amlogic平台基于鼠标设备来构造点击 移动 滑动事件,实现本地模拟,基于方案,模拟头文件如下:
#ifndef _ANALOG_H#define _ANALOG_H#include #include // from /linux/input.htypedef struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value;} input_event;typedef struct analog_event { char *type; char *code; char *value; bool isSync;} analog_event;typedef struct analog_motion { int x; int y;} analog_motion;// 宏定义// method for analog with sceneint init_analog();int close_analog();int send_analog_event(analog_event *event);int analog_click_event();int analog_motion_event(analog_motion *motion);int analog_whell_event(bool is_up);#endif
这里主要是一些方法声明,及业务结构体定义,下面来看下具体实现,基本就是对头文件的实现。这里贴出一些核心实现,次要的忽略
#include #include #include #include #include #include #include #include #include #define MOUSE_KEY_TPYE "1"#define MOUSE_LEFT_KEY_CODE "272"#define REPORT_TYPE 0#define REPORT_CODE 0#define MOUSE_REL_TYPE "2"#define MOUSE_REL_X_CODE "0"#define MOUSE_REL_Y_CODE "1"#define MOUSE_REL_WHEEL_CODE "8"#define WHELL_DOWN "4294967295"#define WHELL_UP "1"#define LOG_TAG "iflytek_hal"static const char *MOUSE_NAME = "/dev/input/event3";static const char *MOUSE_NAME1 ="/dev/input/event4";int fd = -99;static char* get_string(int value);int init_analog(){ if(fd < 0) { fd = open(MOUSE_NAME,O_RDWR); } if(fd < 0) { ALOGI("init_analog_with mouse1"); fd = open(MOUSE_NAME1,O_RDWR); } if(fd < 0) { fprintf(stderr,"could not open %s,%s\n",MOUSE_NAME,strerror(errno)); ALOGE("could not open%s,%s\n",MOUSE_NAME,strerror(errno)); return -1; } ALOGI("init_analog_success"); return fd;}int close_analog() { if(fd > 0) { close(fd); } ALOGI("close_analog"); return 0;}int send_analog_event(analog_event *event){ int ret; if(fd < 0 || event == NULL) { ALOGE("fd is not open or event is null"); return -1; } ALOGI("send_analog_event type is %s,code is %s,value is %s\n",event->type,event->code,event->value); input_event _event; memset(&_event,0,sizeof(_event)); _event.type = atoi(event->type); _event.code = atoi(event->code); _event.value = atoi(event->value); ret = write(fd,&_event,sizeof(_event)); if(ret < sizeof(_event)) { ALOGE("write event error,%s\n",strerror(errno)); return -1; }else { ALOGI("write event size is,%d\n",ret); } if(event->isSync) { memset(&_event,0,sizeof(_event)); _event.type = REPORT_TYPE; _event.code = REPORT_CODE; _event.value = 0; ret = write(fd,&_event,sizeof(_event)); if(ret < sizeof(_event)) { ALOGE("write event error,%s\n",strerror(errno)); return -1; } } ALOGI("send event write event success"); return 0;}int analog_click_event(){ int ret; ALOGI("start analog click event"); analog_event event; memset(&event,0,sizeof(event)); event.type = MOUSE_KEY_TPYE; event.code = MOUSE_LEFT_KEY_CODE; event.value = "1"; event.isSync = true; ret = send_analog_event(&event); if(ret < 0) { return -1; } memset(&event,0,sizeof(event)); event.type = MOUSE_KEY_TPYE; event.code = MOUSE_LEFT_KEY_CODE; event.value = "0"; event.isSync = true; ret = send_analog_event(&event); if(ret < 0) { return -1; } return 0;}int analog_whell_event(bool is_up){ int ret; fprintf(stdout,"start analog whell event"); ALOGI("start analog whell event"); analog_event event; memset(&event,0,sizeof(event)); char *down = WHELL_DOWN; char *up = WHELL_UP; event.type = MOUSE_REL_TYPE; event.code = MOUSE_REL_WHEEL_CODE; event.value = (!is_up) ? down : up; event.isSync = true; ret = send_analog_event(&event); if(ret < 0) { return -1; } return 0;}int analog_motion_event(analog_motion *motion){ bool valid = false; int ret; if(motion == NULL) { fprintf(stderr,"analog_motion is NULL"); ALOGE("analog_motion is NULL"); return -1; } analog_event event; memset(&event,0,sizeof(event)); if(motion->x > 0) { valid = true; event.type = MOUSE_REL_TYPE; event.code = MOUSE_REL_X_CODE; event.value = get_string(motion->x); } if(motion->y > 0) { if(valid) { ret = send_analog_event(&event); if(ret < 0) { return -1; } } valid = true; memset(&event,0,sizeof(event)); event.type = MOUSE_REL_TYPE; event.code = MOUSE_REL_Y_CODE; event.value = get_string(motion->y); } if(valid) { event.isSync = true; ret = send_analog_event(&event); if(ret < 0) { return -1; } } return 0;}int test_main(int argc ,char *argv[]){ int ret; ret = init_analog(); if(ret < 0) { return -1; } if(argc == 2) { if(!strncmp(argv[1],"click",5)) { analog_click_event(); return 0; }else if(!strncmp(argv[1],"up",2)) { return analog_whell_event(true); }else if(!strncmp(argv[1],"down",4)) { return analog_whell_event(false); } return -1; }else if(argc == 3) { analog_motion motion; event.value = get_string(motion->x); } if(motion->y > 0) { if(valid) { ret = send_analog_event(&event); if(ret < 0) { return -1; } } valid = true; memset(&event,0,sizeof(event)); event.type = MOUSE_REL_TYPE; event.code = MOUSE_REL_Y_CODE; event.value = get_string(motion->y); } if(valid) { event.isSync = true; ret = send_analog_event(&event); if(ret < 0) { return -1; } } return 0;}
下面是Android4.4平台下的mk编译文件,目前测试已经通过。
LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_SRC_FILES:= analog.cLOCAL_C_INCLUDES:= bionic/libc/bionicLOCAL_SHARED_LIBRARIES := \ libcutils \ liblog \ libc \ libusbhostLOCAL_MODULE:= analoginclude $(BUILD_EXECUTABLE)~
2.2 Framework层实践案列简述
上文基于Native完成了C端的input测试,为了最终支持App可以应用此接口,针对其逻辑,把它的核心功能分解为HAL及JNI支持模块。
同步的来看下Hal层支持代码,首先看下test.h,它主要定义了hal模块的核心结构体。如下
#ifndef ANDROID_TEST_INTERFACE_H#define ANDROID_TEST_INTERFACE_H#include #ifdef __cplusplus extern "C"{#endif#include "analog.h"#ifdef __cplusplus}#endif__BEGIN_DECLS/** * the id of this module */#define TEST_HARDWARE_MODULE_ID "iflytek"#define ANALOG_DEVICE_ID "analog"#define DEV_MOUSE_INPUT "/dev/input/event4"typedef struct iflytek_module_t { struct hw_module_t common;} iflytek_module_t;typedef struct analog_device_t { struct hw_device_t common; int (*send)(analog_event *event); int (*motion)(analog_motion *motion); int (*init)(); int (*close)();} analog_device_t;static inline int test_open(const struct hw_module_t* module,struct analog_device_t** device) { return module->methods->open(module,ANALOG_DEVICE_ID,(struct hw_device_t**)device);}static inline int test_close(struct analog_device_t* device) { return device->common.close(&device->common);}__END_DECLS#endif
test.c文件主要是基于test.h 在Android框架下的实现。如下,其核心功能依赖上文贴出来的analog.C实现,具体如下:
#define LOG_TAG "test_hal"#include #include #include #include #define MODULE_NAME "test controll module"#define MODULE_AUTHOR "xx"static int test_module_open(const hw_module_t* module,const char* name,hw_device_t** device);static int test_module_close(hw_device_t* device);static struct hw_module_methods_t iflytek_module_methods = { open: test_module_open};test_module_t HAL_MODULE_INFO_SYM = { common: { tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id: TEST_HARDWARE_MODULE_ID, name: MODULE_NAME, author: MODULE_AUTHOR, methods: &test_module_methods }};static int test_module_open(const hw_module_t* module,const char* name,hw_device_t** device) { int status = -EINVAL; if(!strcmp(name,ANALOG_DEVICE_ID)) { analog_device_t *dev; dev = (analog_device_t*)malloc(sizeof(analog_device_t)); if(!dev) { ALOGE("failed malloc analog_device\n"); status = -ENOMEM; return status; } memset(dev,0,sizeof(analog_device_t)); dev->common.tag = HARDWARE_DEVICE_TAG; dev->common.version = 0; dev->common.module = (hw_module_t*)module; dev->common.close = iflytek_module_close; dev->send = send_analog_event; dev->motion = analog_motion_event; dev->init = init_analog; dev->close = close_analog; *device = &dev->common; status =0; }else { ALOGE("faile find %s module\n",name); } ALOGE("open testmodule success\n"); return status;}static int itest_module_close(hw_device_t* device) { analog_device_t *dev = (analog_device_t*) device; dev->close(); return 0;}
这里面重要的是必须定义HAL_MODULE_INFO_SYM变量,其内部初始化了这里定义的模块的各种变量。
其对应的Android.mk文件如下。
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hwLOGCAL_MODULE_TAG := optionalLOCAL_SHARED_LIBRARIES := \ liblog \ libcutils \ libcLOCAL_SRC_FILES := test.cpp analog.cLOCAL_MODULE := test.defaultinclude $(BUILD_SHARED_LIBRARY)
最后若想在App端复用此结果,需要jni层周转,基于amalogic平台测试代码如下,这里仅做参考:
/*** 1 define constant* 2 define function* 3 register protel*/#define LOG_TAG "iflytek_jni"#include "JNIHelp.h"#include "jni.h"#include #include #include #include #include #include #include #include #define CHECK_DEVICE() do{ \ if(!g_device) { \ return; \ } \while(0)#define MOUSE_KEY_TPYE "1"#define MOUSE_LEFT_KEY_CODE "272"#define REPORT_TYPE 0#define REPORT_CODE 0#define MOUSE_REL_TYPE "2"#define MOUSE_REL_X_CODE "0"#define MOUSE_REL_Y_CODE "1"#define MOUSE_REL_WHEEL_CODE "8"#define WHELL_DOWN "4294967295"#define WHELL_UP "1"namespace android {static struct { jclass clazz;} gMotionClassInfo;static analog_device_t* g_device = NULL;static jint android_server_init(JNIEnv* env,jclass clazz) { iflytek_module_t* module; ALOGI("init iflytek hal ..."); if(hw_get_module(IFLYTEK_HARDWARE_MODULE_ID,(const struct hw_module_t**)&module) != 0) { ALOGE("iflytek module is not found"); return -EFAULT; } if(iflytek_open(&module->common,&g_device) != 0) { ALOGE("open iflytek module failed"); return -EFAULT; } return g_device->init();}static void android_server_click(JNIEnv* env,jclass calzz) { ALOGI("start click analog click"); if(!g_device) { ALOGE("g_device is NULL"); return; } analog_event event; memset(&event,0,sizeof(event)); event.type = MOUSE_KEY_TPYE; event.code = MOUSE_LEFT_KEY_CODE; event.value = "1"; event.isSync = true; g_device->send(&event); memset(&event,0,sizeof(event)); event.type = MOUSE_KEY_TPYE; event.code = MOUSE_LEFT_KEY_CODE; event.value = "0"; event.isSync = true; g_device->send(&event); return;}static void android_server_long_click(JNIEnv* env,jclass clazz,jint time) { return;}static void android_server_tap(JNIEnv* env,jclass clazz,jboolean is_up) { ALOGI("start analog tap"); if(!g_device) { ALOGE("g_device is NULL"); return; } analog_event event; memset(&event,0,sizeof(event)); char *down = WHELL_DOWN; char *up = WHELL_UP; event.type = MOUSE_REL_TYPE; event.code = MOUSE_REL_WHEEL_CODE; event.value = (!is_up) ? down : up; event.isSync = true; g_device->send(&event); return;}static void android_server_motion(JNIEnv* env,jclass clazz,jint x_pos,jint y_pos) { ALOGI("start analog motion"); if(!g_device) { ALOGE("g_device is NULL"); return; } analog_motion motion; memset(&motion,0,sizeof(motion)); motion.x = x_pos; motion.y = y_pos; g_device->motion(&motion); return;}#define FIND_CLASS(var, className) \ var = env->FindClass(className); \ LOG_FATAL_IF(! var, "Unable to find class " className);static JNINativeMethod sMethods[] = { {"analogInit","()I",(void*) android_server_init}, {"analogClick","()V",(void*) android_server_click}, {"analogLongClick","(I)V",(void*) android_server_long_click}, {"analogTap","(Z)V",(void*) android_server_tap}, {"analogMotion","(II)V",(void*) android_server_motion},// {"analogMotion","(Lcom/android/MotionEvent;)V",(void*) android_server_iflytek_IflytekService_motion},};int register_android_server_TestService(JNIEnv* env) { FIND_CLASS(gMotionClassInfo.clazz,"com/android/MotionEvent"); return jniRegisterNativeMethods(env,"com/android/server/iflytek/TestService",sMethods,NELEM(sMethods));}}
更多相关文章
- android 双击事件
- Android自学 --点击事件以及弹出菜单
- Android 屏幕适配:最全面的解决方案
- Android之事件处理全面剖析
- Android 模拟滑动 MotionEvent touch事件
- android 弹出的软键盘遮挡住EditText文本框的解决方案
- Android 7.0 虚拟按键(NavigationBar)源码分析 之 View的创建流程
- Android 事件拦截/分发机制 (图解+代码)
- android设置软键盘搜索键以及监听搜索键点击事件