1. 优秀文档
Android 输入系统官方文档: http://source.android.com/devices/input/index.html 中文官方文档
《深入理解 Android 卷 III》第五章 深入理解 Android 输入系统: http://blog.csdn.net/innost/article/details/47660387 主要讲 EventHub
图解 Android - Android GUI 系统 (5) - Android 的 Event Input System: http://www.cnblogs.com/samchen2009/p/3368158.html 关注里面的 Dispatcher 处理流程
2. 输入系统可以分为读取, 分发, 处理三大流程
(1)读取:
a. 要支持多设备, 如 GPIO 键盘, 红外遥控器, USB 键盘鼠标, 触摸屏灯
b. 即插即用, 可以随时插入和拔出.
c. 支持多语言映射, 同一键盘同一按键, code 在不同语言的情况下映射成不同的值.
(2)分发:
a. 分辨
对于按键, 区分是 SystemKey(控制音量, 电源), 还是 GlobalKey(做特殊处理), 还是 UserKey(发给 App 去处理).
对于触摸屏, 有 VirtualKey 和手势.
b. 发送
找出当前 App, 然后发送给它.
(3)处理:
a.App 等收到事件后做处理.
3. 输入子系统的总体流程与参与者
Reader 线程和 Dispatcher 线程与 App 之间的通信使用的就是 socketpair 来实现的.
二, 一个模拟输入设备的 Demo
1. 内核中的框架对输入事件提供了很多套 read/write/ioctl,evdev.c 提供的是原始的数据的读写接口. 原始的数据 Android 中只使用的是它.
mousedev.c ,keyboard.c 加工后的数据. 可以使用 / dev/mouse0/1/2 来获取鼠标加工后的数据, 通过 / dev/input/event0/1/2 获取鼠标的原始数据.
2. 实现一个模拟输入设备驱动
(1)驱动上报事件到 evtest.c 中的缓冲中, 当缓冲中有数据的时候就会唤醒读进程. 由于是模拟的驱动, 不会上报事件, 因此我我们是用 App 向
缓冲中直接写入按键事件数据来模拟事件的上报, App 可用 Android 自带的 sendevent 程序.
(2)怎么写驱动
分配一个 input_devices 结构体, 注册 input_register_devices, 注册后这个设备就会和 evdev.c keyboard.c 建立联系.
注册 input_register_devices 会导致 evdev.c 中的. connect()被调用, 注册之后 evdev.c 中会创建设备节点.
App open()设备节点会导致 evdev.c 中的 open()被调用.
(3)驱动中需要为 Android 构造一些 VID/PID 信息(用于映射), 参见 https://source.android.com/devices/input/key-layout-files
映射文件义. hcm 结尾, 例如 Generic.kcm, 指定组合键的作用, 转换成 Unicode 码等. eg:
- type FULL # FULL: 一种 PC 式全键盘.
- ----------
- key A {
- label: 'A' // 当按键包含一个字符时, 指定物理打印在该按键上的标签. 这是 KeyCharacterMap.getDisplayLabel 方法返回的值.
- base: 'a' // 指定数字文本视图具有焦点时的行为(即应该输入的字符), 例如用户在输入电话号码时.
- shift, capslock: 'A' // 指定在没有按下修饰符时的行为(即应该输入的字符).
- }
3. 设备驱动中, 每个输入设备驱动都有一组 / sys 文件
- input_allocate_device
- dev->dev.type = &input_dev_type;
- .groups = input_dev_attr_groups,
4. 写个脚本自动挂载成 rw 模式
- #!/system/bin/sh
- mount -o remount,rw /system
- alias ls='busybox ls --color'
5. 试验 Demo(基于 Linux3.0 内核)
InputEmulator.c
- /* 参考: drivers\input\keyboard\gpio_keys.c */
- #include <Linux/module.h>
- #include <Linux/version.h>
- #include <Linux/init.h>
- #include <Linux/fs.h>
- #include <Linux/input.h>
- static struct input_dev *input_emulator_dev;
- static int input_emulator_init(void)
- {
- int i;
- /* 1. 分配一个设备结构体 */
- input_emulator_dev = input_allocate_device();;
- /* 2. 设置 */
- /* 2.1 能产生哪类事件 */
- set_bit(EV_KEY, input_emulator_dev->evbit); /* 按键事件 */
- set_bit(EV_REP, input_emulator_dev->evbit); /* 连续按着不松手循环产生按键事件 */
- /* 2.2 设置能产生所有按键事件 */
- for (i = 0; i <BITS_TO_LONGS(KEY_CNT); i++)
- input_emulator_dev->keybit[i] = ~0UL;
- /*
- * 2.3 为 Android 构造一些设备信息, 通过它来寻找映射, 见:
- * https://source.android.com/devices/input/key-layout-files
- */
- input_emulator_dev->name = "InputEmulator.net";
- input_emulator_dev->id.bustype = 1;
- input_emulator_dev->id.vendor = 0x1234;
- input_emulator_dev->id.product = 0x5678;
- input_emulator_dev->id.version = 1;
- /* 3. 注册 */
- input_register_device(input_emulator_dev);
- return 0;
- }
- static void input_emulator_exit(void)
- {
- input_unregister_device(input_emulator_dev);
- input_free_device(input_emulator_dev);
- }
- module_init(input_emulator_init);
- module_exit(input_emulator_exit);
- MODULE_LICENSE("GPL");
- View Code
- Makefile
- KERN_DIR = /media/Ubuntu/works/tiny4412/Linux-3.0.86
- all:
- make -C $(KERN_DIR) M=`pwd` modules
- clean:
- make -C $(KERN_DIR) M=`pwd` modules clean
- rm -rf modules.order
- obj-m += InputEmulator.o
- View Code
Android 中编译驱动模块的方法和 Linux 中一样.
测试:
# insmod InputEmulator.ko
打开浏览器的输入框, 执行以下脚本, 就会发现输入框中输入了 12
- [email protected]:/system/mytest/driver # cat test.sh
- #!/system/bin/sh
- # write 1
- sendevent /dev/input/event5 1 2 1 # 1 2 1 : EV_KEY, KEY_1, down
- sendevent /dev/input/event5 1 2 0 # 1 2 0 : EV_KEY, KEY_1, up
- sendevent /dev/input/event5 0 0 0 # sync
- # write 2
- sendevent /dev/input/event5 1 3 1
- sendevent /dev/input/event5 1 3 0
- sendevent /dev/input/event5 0 0 0
6.hexdump 出来的数据分析:
- # hexdump /dev/input/event5 &
- # ./test.sh
- //tv_sec=4021 0000,tv_usec=2168 0004,type=0001 code=0002,value=0001 0000
- 0000000 4021 0000 2168 0004 0001 0002 0001 0000 //1 2 1
- 0000010 4027 0000 6269 0001 0001 0002 0000 0000 //1 2 0
- 0000020 4027 0000 e524 0001 0000 0000 0000 0000 //sync
- 0000030 4027 0000 81e2 0002 0001 0003 0001 0000
- 0000040 4027 0000 b0e1 0003 0001 0003 0000 0000
- 0000050 4027 0000 b727 0004 0000 0000 0000 0000
应用程序读取到的是 input_event 结构的数组 (copy_to_user() 的是 input_event 结构).
- struct input_event {
- struct timeval time;
- __u16 type;
- __u16 code;
- __s32 value;
- };
- struct timeval {
- __kernel_time_t tv_sec; /* long 类型, 32bit 机器是 4B */
- __kernel_suseconds_t tv_usec; /* long 类型, 32bit 机器是 4B */
- };
7. 触摸屏上可以实现很多虚拟的按键
8. 映射是在 Reader 中做的.
三, Linux 内核输入子系统框架(基于 Linux4.14)
- 1.input_init
- subsys_initcall(input_init); //input.c
- class_register(&input_class); ///sys 文件
- input_proc_init(); // 通过 / proc 中的 handlers 和 devices 可以看到系统中有哪些输入设备 (有属性) 和哪些 handler.
- register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input");
- /*/proc 下的是 3.0 内核的 */
- static int __init input_init(void)
- {
- int err;
- /*# ls /sys/class/input*/
- err = class_register(&input_class);
- if (err) {
- pr_err("unable to register input_dev class\n");
- return err;
- }
- /*
- 在 / proc 下创建 input 目录:
- [email protected]:/proc/bus/input # cat handlers
- N: Number=0 Name=rfkill
- N: Number=1 Name=kbd
- N: Number=2 Name=sysrq (filter)
- N: Number=3 Name=mousedev Minor=32
- N: Number=4 Name=evdev Minor=64
- N: Number=5 Name=cpufreq_interactive
- [email protected]:/proc/bus/input # cat devices
- ......
- I: Bus=0001 Vendor=1234 Product=5678 Version=0001 // 自己 input 设备驱动中赋值的.
- N: Name="InputEmulator.net"
- P: Phys=
- S: Sysfs=/devices/virtual/input/input5
- U: Uniq=
- H: Handlers=sysrq rfkill kbd event5
- B: PROP=0
- B: EV=100003
- B: KEY=ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff... // 支持的 key 类型的键值, 每一 bit 表示一个.
- */
- err = input_proc_init();
- if (err)
- goto fail1;
- /* 主设备号从 13 开始 */
- err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input");
- if (err) {
- pr_err("unable to register char major %d", INPUT_MAJOR);
- goto fail2;
- }
- return 0;
- fail2:
- input_proc_exit();
- fail1:
- class_unregister(&input_class);
- return err;
- }
- View Code
2.evdev.c 注册和向 App 提供访问 event 节点的接口
- evdev_init
- input_register_handler(&evdev_handler);
- /*
- 两个全局链表 input_handler_list 和 input_dev_list
- 所有注册的 input_handler 结构都通过其 node 域挂接在 input_handler_list 上
- 所有注册的 input_dev 结构都通过其 node 域挂接在 input_dev_list 上
- 无论 handler 被注册还是 device 被注册, 都会触发匹配, 调用 input_attach_handler
- */
- input_attach_handler(struct input_dev *dev, struct input_handler *handler) //input.c
- /*
- 对 handler 的 input_device_id 数组中的每一项都进行比较, 若其 flag 匹配上
- 且 input_dev 的所有事件 bit 都是 handler 的 input_device_id 中指定的子集,
- 才不执行 return false.
- */
- input_match_device(handler, dev);
- /*
- 匹配上后调用 handler 的 connect()里面会注册 input_handle, 和 / dev/input/eventX, 这里分析的是 evdev.c 这个 handler, 因此就只看
- 它的 hander->connect()即 evdev_connect().
- */
- handler->connect(handler, dev, id);
- /* 创建一个 input_handle, 其 d_node 和 h_node 链表上分别挂载的是匹配的 input_dev 和 input_handler*/
- input_register_handle(&evdev->handle); //evdev.c 注册一个 input_handle, 注意不是 input_handler.
- /*
- 创建设备节点 / dev/input/eventX 设备节点, 其 ops 为 evdev_fops,
- 也就是说应用程序访问的 event 节点是这里创建的, ops 为 evdev_fops
- */
- cdev_init(&evdev->cdev, &evdev_fops);
- cdev_device_add(&evdev->cdev, &evdev->dev);
3. 调用 input_register_handler()注册 handler 的文件有:
evdev.c input-leds.c joydev.c keyboard.c kgdboc.c mac_hid.c mousedev.c sysrq.c
4. 事件上报:
- input_report_key
- handler->events(handle, vals, count);
- evdev_events //evdev.c
- wake_up_interruptible(&evdev->wait); // 上报事件并唤醒 App
5.App 也可以向 eventX 设备节点写入数据, 这会促使 input_dev.event()被调用.
6.int bitmap_subset(const unsigned long *src1, const unsigned long *src2, unsigned int nbits)
功能: 判断 src2 是否是 src1 的子集, 是返回 1, 不是返回 0
Android 输入系统(2)-- 输入系统框架(Android+Linux)
来源: http://www.bubuko.com/infodetail-3057698.html