系列文章目录
目录
2.6 EventHub::loadConfigurationLocked
2.7 getInputDeviceConfigurationFilePathByDeviceIdentifier
2.8 getInputDeviceConfigurationFilePathByName
2.9 appendInputDeviceConfigurationFileRelativePath
2.12 registerDeviceForEpollLocked
2.18 TouchInputMapper::configure
2.19 TouchInputMapper::configureParameters
2.20 CursorScrollAccumulator::configure
2.21 TouchButtonAccumulator::configure
2.22 MultiTouchInputMapper::configureRawPointerAxes
2.23 InputMapper::getAbsoluteAxisInfo
2.24 EventHub::getAbsoluteAxisInfo
2.25 MultiTouchMotionAccumulator::configure
2.26 TouchInputMapper::parseCalibration
2.27 TouchInputMapper::resolveCalibration
2.28 TouchInputMapper::configureSurface
2.29 InputReaderConfiguration::getDisplayViewport
2.30 TouchInputMapper::configureVirtualKeys
1.简介
从之前的篇幅我们知道了,事件分为设备增删事件和原始输入事件,而原始输入事件主要有两种,一种是key按键事件的派发,一种是触摸事件的派发。Key事件的派发我们已经分析过了,本篇主要针对motion事件的派发。想要了解motion事件的派发,首先需要了解到touch设备的是如何加载的,本篇和前文的input设备流程相同,只是对于触摸屏的配置进行了更详细的描述。如MultiTouchInputMapper的创建过程。
1.1流程介绍
第一步:经过前面几篇文章我们知道,在开机启动后,input系统也在systemserver进程中被拉起,InputReader线程中的loopOnce会执行,loopOnce会执行getEvents函数,此函数在设备刚启动时,会调用scanDevicesLocked函数扫描/dev/input下的所有输入设备。
第二步:当扫描到/dev/input/xxx的设备时,会调用openDeviceLocked函数打开设备节点,并从中读取设备的厂商信息,如触摸屏配置文件路径等。
第三步:然后会调用loadConfigurationLocked去加载指定路径的配置文件将里面的内容加载到对应的map中,如触摸屏的Vendor_xxxx_Product_xxxx.idc文件。
第四步:会此设备注册到epoll中监听设备的原始输入事件。
第五步:生成设备添加事件。
第六步:processEventsLocked处理设备添加事件。此时会根据设备创建多个mapper对象,以触摸屏为例子,则会创建MultiTouchInputMapper对象。
第七步:然后便是对MultiTouchInputMapper进行配置,会调用configure函数。
此函数有以下几个作用:
1.对触摸屏判断多触摸还是单触摸,触摸设备是触摸屏还是触摸平板等
2.读取屏幕的信息,构建物理坐标系,如x和y的范围等信息。
3.准备输入设备的校准,即mConfiguration中读取大小,方向,压力等值。
4. 配置device source、Surface尺寸、方向和缩放比例。完成物理坐标系到屏幕坐标系的转化。
第八步:等待触摸事件的到来。
1.2 时序图
(为了完整的画出时序图,较为模糊,可保存到本地放大查看)
2.touch设备的加载和配置流程分析
首先我们从前文知道当input系统第一次启动时,会扫描/dev/input下的所有设备节点,然后读取所有设备节点的信息,并生成对应的mapper对象,那么本篇便来分析常用的触摸屏的加载具体会做些什么。
首先从启动篇我们知道,启动后,inputreader线程会执行loopOnce函数。前面的内容在启动篇已经介绍过了,所以本篇我们便从此处开始分析。
2.1 InputReader::loopOnce
主要作用为:
1. 通过EventHub的getEvents函数获取add设备事件。
2. processEventsLocked处理设备事件。
3.发布事件,通知inputdispatcher线程去处理消息。
//第一次启动时,执行的代码块
void InputReader::loopOnce() {
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//通过EventHub的getEvents函数获取事件,
//并存放在mEventBuffer中。此时可以知道刚启动时,会生成所有已经扫描的设备的add类型的事件
//参数分析:timeoutMillis=-1,如果数字为0代表立即执行 -1为一直阻塞
//mEventBuffer是一个存放从eventhub中读取的rawevent结构体类型的数组,源代码是:RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
//EVENT_BUFFER_SIZE值是256,代表最大可以读取256个原始事件,RawEvent结构体如下
从EventHub检索到的原始事件
//struct RawEvent {
//nsecs_t when;//时间
//int32_t deviceId;//事件发生的设备id
//int32_t type;//类型,例如按键事件等
//int32_t code;//扫描码,按键对应的扫描码
//int32_t value;//值,表示按键按下,或者抬起等
//};
if (count)
{//返回的事件数量大于,则调用processEventsLocked处理事件
processEventsLocked(mEventBuffer, count);
}
if (oldGeneration != mGeneration)
{//此处是不等于的,因为当有add设备事件时processEventsLocked函数中调用addDeviceLocked,addDeviceLocked在完成设备配置后
//会调用bumpGenerationLocked函数,使得mGeneration+1,从而不相等
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);//获取所有的新的设备信息到inputDevices容器中
}
//发布事件。 一般此处只会处理加工后的原始输入事件,而此时开机启动只有触摸屏幕add设备事件
mQueuedListener->flush();
}
2.2 EventHub::getEvents
主要作用为:
1.设备刚启动,调用scanDevicesLocked函数扫描/dev/input下的所有输入设备,用Device类保存其信息,并生成DEVICE_ADDED类型的事件(设备增加类型事件)放入消息数组中
2.返回存在设备增加事件的数组,让inputreader进行处理。
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
for (;;) {//死循环
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);//获取当前时间
if (mNeedToScanDevices) {//默认值为true,即第一次调用getevent时,会扫描/dev/input下的设备
mNeedToScanDevices = false;//设置为false,避免重复扫描设备
scanDevicesLocked();//执行扫描设备的函数。扫描/dev/input下的设备
mNeedToSendFinishedDeviceScan = true;//mNeedToSendFinishedDeviceScan值为true,用于生成FINISHED_DEVICE_SCAN事件
}
while (mOpeningDevices != NULL) {
Device* device = mOpeningDevices;//此时mOpeningDevices值是刚打开过的device
mOpeningDevices = device->next;//device->next值为0,指向下一个设备
//生成DEVICE_ADDED类型的事件,并通过event指针存放于buffer数组中
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;//如果是内置键盘,则其id为0,如果不是则是device->id
event->type = DEVICE_ADDED;//类型
event += 1;//event的定义是RawEvent* event = buffer,event指针指向传入的buffer数组的首地址,即第0个元素,
//每存入一个事件,event指针向后移动一个元素
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {//buffer数组可容量-1
break;
}
}
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
//生成FINISHED_DEVICE_SCAN类型的事件,并通过event指针存放于buffer数组中
event->when = now;
event->type = FINISHED_DEVICE_SCAN;
event += 1;
if (--capacity == 0) {
break;
}
}
bool deviceChanged = false;
// 当event的指针不再指向buffer的首地址时,代表里面有数据,或者被唤醒时,立即退出循环
if (event != buffer || awoken) {//此时生成了两种事件,一个是扫描的所有打开设备的事件,还有一个是完成设备扫描报告的事件,故退出循环
break;
}
}
// 全部完成后,返回我们读取的事件数。
return event - buffer;
}
2.3 scanDevicesLocked
void EventHub::scanDevicesLocked() {
status_t res = scanDirLocked(DEVICE_PATH);//调用scanDirLocked方法,DEVICE_PATH=/dev/input,返回0代表成功
if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {//
//mDevices的定义是一个map容器,KeyedVector<int32_t, Device*> mDevices;mDevices中存放的是已经扫描完成的设备,即
//已经完成设备厂商信息的获取和注册进epoll监听的设备。VIRTUAL_KEYBOARD_ID的值等于-1
//此处如果从mDevice中没有找到虚拟键盘,则调用createVirtualKeyboardLocked方法创建虚拟键盘。
createVirtualKeyboardLocked();
}
}
2.4 scanDirLocked
1.循环打开/dev/input下的所有设备节点。
status_t EventHub::scanDirLocked(const char *dirname)
{
char devname[PATH_MAX];
char *filename;
DIR *dir;
struct dirent *de;
dir = opendir(dirname);
if(dir == NULL)
return -1;
strcpy(devname, dirname);
filename = devname + strlen(devname);
*filename++ = '/';
while((de = readdir(dir))) {//循环打开目录下的所有设备
if(de->d_name[0] == '.' &&
(de->d_name[1] == '\0' ||
(de->d_name[1] == '.' && de->d_name[2] == '\0')))
continue;
strcpy(filename, de->d_name);
openDeviceLocked(devname);//打开设备
}
closedir(dir);//关闭目录
return 0;
}
2.5 openDeviceLocked
针对触摸屏的主要作用为:
主要作用为:
1.打开/dev/input/xxxx的设备,然后获取设备的驱动版本,设备产品,设备供应商,设备物理地址,设备标识符将信息保存到InputDeviceIdentifier类的identifier对象中,此对象主要用于存储设备厂商的所有信息。
2.假设此时有一个设备/dev/input/event0,根据其dentifier对象生成对应的Device类对象。然后加载设备的配置文件到device类对象的PropertyMap中。
配置文件主要有三类。
一.idc文件,主要用于触摸屏配置。
二.kl文件,主要用于键盘的扫描码和keycode的转化。
三.kcm文件,主要作用是将 Android按键代码与修饰符的组合映射到 Unicode。
3.从设备的fd中读取数据判断其设备类型为EV_KEY、EV_ABS、EV_REL、EV_SW、EV_LED、EV_FF类型。
一:EV_KEY,按键类型的事件。能够上报这类事件的设备有键盘、鼠标、手柄、手写板
等一切拥有按钮的设备(包括手机上的实体按键)。在Device结构体中,对应的事件
位掩码keyBitmask描述了设备可以产生的按键事件的集合。按键事件的全集包括字
符按键、方向键、控制键、鼠标键、游戏按键等。
二:EV_ABS,绝对坐标类型的事件。这类事件描述了在空间中的一个点,触控板、触
摸屏等使用绝对坐标的输入设备可以上报这类事件。事件位掩码absBitmask描述了
设备可以上报的事件的维度信息(ABS_X、ABS_Y、ABS_Z),以及是否支持多点
事件。
三:EV_REL,相对坐标类型的事件。这类事件描述了事件在空间中相对于上次事件
的偏移量。鼠标、轨迹球等基于游标指针的设备可以上报此类事件。事件位掩码
relBitmask描述了设备可以上报的事件的维度信息(REL_X、REL_Y、REL_Z)。
四:EV_SW,开关类型的事件。这类事件描述了若干固定状态之间的切换。手机上的静
音模式开关按钮、模式切换拨盘等设备可以上报此类事件。事件位掩码swBitmask表
示了设备可切换的状态列表。
五:EV_LED,光反馈类型事件。ledBitmask描述了设备是否支持光
六:EV_FF,力反馈类型,ffBitmask则描述了设备是否支持力反馈。
4.根据上诉大类型,生成更细分的类型——INPUT_DEVICE_CLASS_xxx类型。如L按键事件可再通过有无X,y比特位,区分是键盘还是蓝牙手柄。
一:INPUT_DEVICE_CLASS_KEYBOARD,可以上报鼠标按键以外的EV_KEY类型事件的设备都属于此类。如键盘、机身按钮(音量键、电源键等)。
二:INPUT_DEVICE_CLASS_ALPHAKEY,可以上报字符按键的设备,例如键盘。此类 型的设备必定同时属于KEYBOARD。
三:INPUT_DEVICE_CLASS_DPAD,可以上报方向键的设备。例如键盘、手机导航键
等。这类设备同时也属于KEYBOARD。
四:INPUT_DEVICE_CLASS_GAMEPAD,可以上报游戏按键的设备,如游戏手柄。这类设备同时也属于KEYBOARD。
五:INPUT_DEVICE_CLASS_TOUCH,可以上报EV_ABS类型事件的设备都属于此类,如触摸屏和触控板。
六:INPUT_DEVICE_CLASS_TOUCH_MT,可以上报EV_ABS类型事件,并且其事件位掩码指示其支持多点事件的设备属于此类。例如多点触摸屏。这类设备同时也属于TOUCH类型。
七:INPUT_DEVICE_CLASS_CURSOR,可以上报EV_REL类型的事件,并且可以上报BTN_MOUSE子类的EV_KEY事件的设备属于此类,例如鼠标和轨迹球。
八:INPUT_DEVICE_CLASS_SWITCH,可以上报EV_SW类型事件的设备。
九:INPUT_DEVICE_CLASS_JOYSTICK,属于GAMEPAD类型,并且属于TOUCH类型的设备。
十:INPUT_DEVICE_CLASS_VIBRATOR,支持力反馈的设备。
十一:INPUT_DEVICE_CLASS_VIRTUAL,虚拟设备。
十二:INPUT_DEVICE_CLASS_EXTERNAL,外部设备,即非内建设备。如外接鼠标、键盘、游戏手柄等。
5.然后注册设备节点到epoll中,开始监听各个设备节点下是否有可读事件,即监听此设备的原始输入事件。
status_t EventHub::openDeviceLocked(const char *devicePath) {
char buffer[80];
int fd = open(devicePath, O_RDWR | O_CLOEXEC | O_NONBLOCK);//以读写,非阻塞,原子方式打开设备
//打开设备节点的文件描述符,用于获取设备信息以及读取原始输入事件
InputDeviceIdentifier identifier;//存储厂商的信息
//接下来的代码通过ioctl()函数从设备节点中获取输入设备的厂商信息
if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {//通过设备的fd,向驱动发送EVIOCGNAME指令,
//获取设备名称,写入buffer中
} else {
buffer[sizeof(buffer) - 1] = '\0';
identifier.name.setTo(buffer);//从buffer中取出名称信息,赋值给identifier的name属性
}
// 检查获取的设备名称是否在排除列表中下
for (size_t i = 0; i < mExcludedDevices.size(); i++) {// 检查获取的设备名称是否在排除列表中下,
//如果在mExcludedDevices列表中,则关闭此设备,并返回,mExcludedDevices列表在setExcludedDevices方法中赋值的,
//而setExcludedDevices方法是在inputreader构造函数中的refreshConfigurationLocked方法中调用的,
//同时mExcludedDevices最后是调用到java层ims中获取的。
const String8& item = mExcludedDevices.itemAt(i);
if (identifier.name == item) {
ALOGI("ignoring event id %s driver %s\n", devicePath, item.string());
close(fd);
return -1;
}
}
//获取设备驱动版本
int driverVersion;
if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {//从驱动中获取的信息赋值给driverVersion
ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));
close(fd);
return -1;
}
//获取设备标识符
struct input_id inputId;
if(ioctl(fd, EVIOCGID, &inputId)) {//赋值给inputId
ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));
close(fd);
return -1;
}
identifier.bus = inputId.bustype;
identifier.product = inputId.product;//设备产品
identifier.vendor = inputId.vendor;//供应商
identifier.version = inputId.version;//版本
// 获取设备物理地址
if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {//放于buffer中
//fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
} else {
buffer[sizeof(buffer) - 1] = '\0';
identifier.location.setTo(buffer);//赋值给location
}
//获取设备唯一的id
if(ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) {
//fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno));
} else {
buffer[sizeof(buffer) - 1] = '\0';
identifier.uniqueId.setTo(buffer);//赋值给uniqueId
}
assignDescriptorLocked(identifier);//将identifier信息填充到fd,分配唯一标识设备的设备描述符,主要作用是填写identifier.nonce字段。
// 分配device,device对象获取fd的所有权
int32_t deviceId = mNextDeviceId++;//mNextDeviceId初始值为1,故deviceId=1
Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
//参数分析:
//此处结合是getevent工具分析的
//fd是dev/input/event0的设备的描述符
//deviceid=1
//devicePath=dev/input/event0
//identifier存储了此设备所有的厂商信息
loadConfigurationLocked(device);//加载device的配置文件。主要是例如:device.type和device.internal等信息
// 详细事件报告的类型,从fd对应的device中读取各种信息,并保存到device的keyBitmask或absBitmask中,
//例如:如果是键盘设备,则device->keyBitmask有数据,而device->absBitmask无数据
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);//按键类型
ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);//绝对坐标类型
ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);//相对坐标类型,只要用于motion
ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);//开关类型
ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);//led等类型
ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);//力反馈类型
ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
//Device结构体的事件位掩码描述了6种类型的输入事件:
//EV_KEY,按键类型的事件。能够上报这类事件的设备有键盘、鼠标、手柄、手写板
//等一切拥有按钮的设备(包括手机上的实体按键)。在Device结构体中,对应的事件
//位掩码keyBitmask描述了设备可以产生的按键事件的集合。按键事件的全集包括字
//符按键、方向键、控制键、鼠标键、游戏按键等。
//EV_ABS,绝对坐标类型的事件。这类事件描述了在空间中的一个点,触控板、触
//摸屏等使用绝对坐标的输入设备可以上报这类事件。事件位掩码absBitmask描述了
//设备可以上报的事件的维度信息(ABS_X、ABS_Y、ABS_Z),以及是否支持多点
//事件。
//EV_REL,相对坐标类型的事件。这类事件描述了事件在空间中相对于上次事件
//的偏移量。鼠标、轨迹球等基于游标指针的设备可以上报此类事件。事件位掩码
//relBitmask描述了设备可以上报的事件的维度信息(REL_X、REL_Y、REL_Z)。
//EV_SW,开关类型的事件。这类事件描述了若干固定状态之间的切换。手机上的静
//音模式开关按钮、模式切换拨盘等设备可以上报此类事件。事件位掩码swBitmask表
//示了设备可切换的状态列表。
//EV_LED,光反馈类型事件。ledBitmask描述了设备是否支持光
//EV_FF,力反馈类型,ffBitmask则描述了设备是否支持力反馈。
//INPUT_DEVICE_CLASS_KEYBOARD,可以上报鼠标按键以外的EV_KEY类型事件的设备都属于此类。如键
//盘、机身按钮(音量键、电源键等)。
//INPUT_DEVICE_CLASS_ALPHAKEY,可以上报字符按键的设备,例如键盘。此类型的设备必定同时属于KEYBOARD。
//INPUT_DEVICE_CLASS_DPAD,可以上报方向键的设备。例如键盘、手机导航键等。这类设备同时也属于KEYBOARD。
//INPUT_DEVICE_CLASS_GAMEPAD,可以上报游戏按键的设备,如游戏手柄。这类设备同时也属于KEYBOARD。
//INPUT_DEVICE_CLASS_TOUCH,可以上报EV_ABS类型事件的设备都属于此类,如触摸屏和触控板。
//INPUT_DEVICE_CLASS_TOUCH_MT,可以上报EV_ABS类型事件,并且其事件位掩码指示其支持多点事件
//的设备属于此类。例如多点触摸屏。这类设备同时也属于TOUCH类型。
//INPUT_DEVICE_CLASS_CURSOR,可以上报EV_REL类型的事件,并且可以上报BTN_MOUSE子类的
//EV_KEY事件的设备属于此类,例如鼠标和轨迹球。
//INPUT_DEVICE_CLASS_SWITCH,可以上报EV_SW类型事件的设备。
//INPUT_DEVICE_CLASS_JOYSTICK,属于GAMEPAD类型,并且属于TOUCH类型的设备。
//INPUT_DEVICE_CLASS_VIBRATOR,支持力反馈的设备。
//INPUT_DEVICE_CLASS_VIRTUAL,虚拟设备。
//INPUT_DEVICE_CLASS_EXTERNAL,外部设备,即非内建设备。如外接鼠标、键盘、游戏手柄等。
//通过查看device->absBitmask,查看这是不是触摸板。
if (test_bit(ABS_MT_POSITION_X, device->absBitmask)
&& test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {//尝试确认设备确实触摸屏。
device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
}
// 查看这是不是旧式的单触的驱动
} else if (test_bit(BTN_TOUCH, device->keyBitmask)
&& test_bit(ABS_X, device->absBitmask)
&& test_bit(ABS_Y, device->absBitmask)) {
device->classes |= INPUT_DEVICE_CLASS_TOUCH;
}
// 如果是触摸屏,则为其配置虚拟键盘
if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
//加载触摸屏的虚拟键(如果有)。
status_t status = loadVirtualKeyMapLocked(device);//loadVirtualKeyMapLocked函数的作用大致为
//加载系统提供的虚拟键盘的文件,然后将其键值对保存到virtualKeyMap中
if (!status) {
device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
}
}
// 配置键盘、游戏板或虚拟键盘。
if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
if (!keyMapStatus
&& mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD//表示此时并未任何内置键盘配置过
&& isEligibleBuiltInKeyboard(device->identifier,
device->configuration, &device->keyMap)) //如果符合条件,会将键盘注册为内置键盘,
//然后device->keyMap保存着键盘映射表信息
{
mBuiltInKeyboardId = device->id;//mBuiltInKeyboardId内置键盘id,如果是第一次打开设备,则此时id为1
}
}
if (registerDeviceForEpollLocked(device)