Android模拟输入操作的实现与原理

在Android系统的使用中,我们通过各种输入方式对手机进行控制,那么下面我们来了解怎样通过代码去进行输入操作。

getevent/sendevent简介

想要模拟输入操作,我们必须了解Android系统提供的getevent/sendevent工具

工具源码位于Android SDK的system/core/toolbox下(sendevent.c getevent.c)

getevent监听输入设备节点的内容,当输入事件被写入到节点中时,getevent会将其读出并打印在屏幕上。由于getevent不会对事件数据做任何加工,因此其输出的内容是由内核提供的最原始的事件。

sendevent用于发送input事件,sendevent发送事件到系统时,会出发输入系统,从而对事件进行解析执行。

本文测试机Nexus 6P

getevent使用

运行如下命令,可查看getevent的使用方法以及参数。

adb shell getevent -h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
C:\Users\yanjie.xu>adb shell getevent -h
Usage: getevent [-t] [-n] [-s switchmask] [-S] [-v [mask]] [-d] [-p] [-i] [-l] [-q] [-c count] [-r] [device]
-t: show time stamps
-n: don't print newlines
-s: print switch states for given bits
-S: print all switch states
-v: verbosity mask (errs=1, dev=2, name=4, info=8, vers=16, pos. events=32, props=64)
-d: show HID descriptor, if available
-p: show possible events (errs, dev, name, pos. events)
-i: show all device info and possible events
-l: label event types and names in plain text
-q: quiet (clear verbosity mask)
-c: print given number of events then exit
-r: print rate events are received

[-t] 参数显示事件的时间戳

[-n] 取消事件显示时的换行符

[-s switchmask] 得到指定位的开关状态

[-S] 得到所有开关的状态

[-v [mask]] 根据mask的值显示相关信息,后面详细介绍mask的使用方法

[-p] 显示每个设备支持的事件类型和编码

[-q] 只显示事件数据

[-c count] 只显示count次事件的数据

[-r] 显示事件接收频率。

运行如下命令,查看每个设备支持的事件类型,编码,值,后面发送事件的时候我们会用到这些信息

adb shell getevent -p
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
C:\Users\yanjie.xu>adb shell getevent -p
add device 1: /dev/input/event6
name: "uinput-folio"
events:
SW (0005): 0000
input props:
<none>
add device 2: /dev/input/event5
name: "msm8994-tomtom-mtp-snd-card Headset Jack"
events:
SW (0005): 0002 0004 0006 0007 000e 000f 0010 0011
input props:
<none>
add device 3: /dev/input/event4
name: "msm8994-tomtom-mtp-snd-card Button Jack"
events:
KEY (0001): 0072 0073 00e2 0104 0105 0106 0107 0246
input props:
INPUT_PROP_TOPBUTTONPAD
add device 4: /dev/input/event2
name: "qpnp_pon"
events:
KEY (0001): 0072 0074
input props:
INPUT_PROP_TOPBUTTONPAD
add device 5: /dev/input/event1
name: "STM VL6180 proximity sensor"
events:
ABS (0003): 0010 : value 1508329013, min 0, max -1, fuzz 0, flat 0, resolution 0
0011 : value 152091, min 0, max -1, fuzz 0, flat 0, resolution 0
0012 : value 765, min 0, max 765, fuzz 0, flat 0, resolution 0
0013 : value 8, min 0, max 255, fuzz 0, flat 0, resolution 0
0014 : value 26, min 0, max -1, fuzz 0, flat 0, resolution 0
0015 : value 32, min 0, max -1, fuzz 0, flat 0, resolution 0
0016 : value 49583, min 0, max -1, fuzz 0, flat 0, resolution 0
0017 : value 207, min 0, max -1, fuzz 0, flat 0, resolution 0
0019 : value 77, min 0, max 76, fuzz 0, flat 0, resolution 0
input props:
<none>
add device 6: /dev/input/event3
name: "gpio-keys"
events:
KEY (0001): 0073 0210 02fe
input props:
<none>
add device 7: /dev/input/event0
name: "synaptics_dsx"
events:
KEY (0001): 008f 0145 014a
ABS (0003): 0000 : value 0, min 0, max 1439, fuzz 0, flat 0, resolution 0
0001 : value 0, min 0, max 2559, fuzz 0, flat 0, resolution 0
002f : value 0, min 0, max 9, fuzz 0, flat 0, resolution 0
0030 : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
0031 : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
0035 : value 0, min 0, max 1439, fuzz 0, flat 0, resolution 0
0036 : value 0, min 0, max 2559, fuzz 0, flat 0, resolution 0
0039 : value 0, min 0, max 65535, fuzz 0, flat 0, resolution 0
input props:
INPUT_PROP_DIRECT
could not get driver version for /dev/input/mice, Not a typewriter
could not get driver version for /dev/input/mouse0, Not a typewriter

打印信息中显示了当前系统所存在的所有输入设备列表,列表内容包括了输入设备支持的事件类型,编码,值。

其定义可参考源码

\kernel-3.18\include\linux\input.h

1
2
3
4
5
6
7
8
9
10
11
/**
* struct input_value - input value representation
* @type: type of value (EV_KEY, EV_ABS, etc)
* @code: the value code
* @value: the value
*/
struct input_value {
__u16 type;
__u16 code;
__s32 value;
};

每个device代表一个输入设备,而KEY (0001), REL (0002), ABS (0003)等表示该设备支持的事件类型。

Linux的Input设备支持的事件类型:

EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS 0x03 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC 0x04 其它
EV_SW 0x05 开关
EV_LED 0x11 按键/设备灯
EV_SND 0x12 声音/警报
EV_REP 0x14 重复
EV_FF 0x15 力反馈
EV_PWR 0x16 电源
EV_FF_STATUS 0x17 力反馈状态
EV_MAX 0x1f 事件类型最大个数和提供位掩码支持

事件类型后面的编码,值,所代表的操作可通过以下代码查看

\frameworks\base\data\keyboards\qwerty.kl 如果没有其他配置,则默认使用qwerty.kl映射

\frameworks\base\data\keyboards\Generic.kl Generic.kl包含了所有的键,优先级低于其他kl文件,一般不建议修改

\frameworks\native\include\android\keycodes.h 此文件定义了所有的输入设备的操作代码

adb shell getevent

通过上面命令我们可以精确的获取到某个输入设备的操作

运行命令->操作手机->打印出相关操作

信息的对应关系如下:

1
2
3
4
5
                   type code value
/dev/input/event0: 0001 0145 00000000
/dev/input/event0: 0000 0000 00000000
/dev/input/event0: 0003 0039 00006a54
/dev/input/event0: 0001 014a 00000001

device:指的是处理触摸和按键的输入设备。
type:指的是事件类型
code 指的是前面type代表的事件中支持的编码。
value 指的是值。

sendevent使用

sendevent [device] [type] [code] [value]

通过上面命令我们可以直接做到模拟输入,可以看到sendevent需要4个参数,这些值可以由input子系统定义,也可以从getevent里面获取。

例如:

1
sendevent /dev/input/event0 1 158 1

type为1表示是按键事件;value为1表示按下,为0表示弹起,一次按键事件由按下和弹起两个操作组成。

如在屏幕的x坐标为40,y坐标为210的点上touch一下(六组命令必须配合使用,缺一不可)

1
2
3
4
5
6
adb shell sendevent /dev/input/event0 3 0 40
adb shell sendevent /dev/input/event0 3 1 210
adb shell sendevent /dev/input/event0 1 330 1 //touch
adb shell sendevent /dev/input/event0 0 0 0 //it must have
adb shell sendevent /dev/input/event0 1 330 0 //untouch
adb shell sendevent /dev/input/event0 0 0 0 //it must have

input命令使用

adb shell input text //输入内容

adb shell input keyevent //物理按键或屏幕按键

adb shell input tap //touch事件

adb shell input swipe //滑动事件

input keyevent 几个比较常用的code值:

1
2
3
4
5
6
7
8
9
10
input keyevent 3    // Home
input keyevent 4 // Back
input keyevent 19 //Up
input keyevent 20 //Down
input keyevent 21 //Left
input keyevent 22 //Right
input keyevent 23 //Select/Ok
input keyevent 24 //Volume+
input keyevent 25 // Volume-
input keyevent 82 // Menu 菜单

使用代码实现模拟按键

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** 
* 执行shell命令
*
* @param cmd
*/
private void execShellCmd(String cmd) {
try {
// 申请获取root权限,这一步很重要,不然会没有作用
Process process = Runtime.getRuntime().exec("su");
// 获取输出流
OutputStream outputStream = process.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(
outputStream);
dataOutputStream.writeBytes(cmd);
dataOutputStream.flush();
dataOutputStream.close();
outputStream.close();
} catch (Throwable t) {
t.printStackTrace();
}
}

调用案例:

1
2
3
4
5
6
7
execShellCmd("getevent -p");  
execShellCmd("sendevent /dev/input/event0 1 158 1");
execShellCmd("sendevent /dev/input/event0 1 158 0");
execShellCmd("input keyevent 3");//home
execShellCmd("input text 'helloworld!' ");
execShellCmd("input tap 168 252");
execShellCmd("input swipe 100 250 200 280");