一、InputReader线程
- getEvents。通过EventHub(监听目录/dev/input)读取input_event格式的事件放入mEventBuffer,mEventBuffer是一个大小为256的RawEvent数组。
- processEventsLocked。对事件进行加工,转换RawEvent -> NotifyKeyArgs/NotifyMotionArgs(基类NotifyArgs),将NotifyArgs放在QueuedInputListener的mArgsQueue队列。
- mQueuedListener->flush。依次提取mArgsQueue中事件,转换NotifyKeyArgs/NotifyMotionArgs -> KeyEntry/MotionEntry(基类EventEntry),并将EventEntry发送到InputDispatcher的mInboundQueue队列(新事件放在尾部),并唤醒InputDispatcher线程。
1.1 input_event、RawEvent、deviceId
<aosp>/kernel/include/uapi/linux/input.h struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; };
input_event是linux内核定义的、封装输入事件的数据结构。type指示这是哪种事件,像移动坐标是EV_ABS(0x3),code针对具体type有不同意义,它指示后面跟的value语义。当type=EV_ABS、code=ABS_MT_POSITION_X,指示value存储着移动到的x座标,code是ABS_MT_POSITION_Y时,指示value存储着移动到的y座标。从这里也看出,虽然移动是一个事件,但它会被拆成两个input_event。
<aosp>/frameworks/native/services/inputflinger/EventHub.h struct RawEvent { nsecs_t when; int32_t deviceId; int32_t type; int32_t code; int32_t value; };
- type。对应input_event中的type。
- code。对应input_event中的code。
- value。对应input_event中的value。
deviceId在mDevices唯一标识Device。它和linux层设备驱动无关,是EventHub往KeyedVector<int32_t, Device*> mDevices添加Device时,算出的唯一int32_t值。第一个设备的值是1,每新增加一个设备该值就加1。它也是mDevices中的first。后续模块可由该值找到Device。该值也存在于epool事件中的epoll_event.data.u32,通过它,getEvents可由epoll_event找出产生该事件的Device。
int32_t deviceId = mNextDeviceId++; Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
deviceId在后绪处理到处在用,还传到app。
deviceId --> InputDevice.mId --> NotifyMotionArgs.deviceId --> InputEvent.mDeviceId
1.2 创建、移除设备
class Device { // 设备文件描述任,这个描述符在openDeviceLocked会加入到epool。 int fd; // 设备在mDevices集合中的标识,即上面说的deviceId。 int32_t id; // 在/dev中的路径,像/dev/input/input5 String8 path; const InputDeviceIdentifier identifier; };
如何检测到设备增加、删除?——采用INotify机制,该机制可以检测到某一目录下文件的创建、删除。具体到这里就是/dev/input目录下设备文件的创建、删除。以下是创建设备的步骤。
- 创建监听“/dev/input”目录的INotify文件描述符mINotifyFd,把它加入epool描述符mEpollFd。
- 执行scanDevicesLocked。它会枚举/dev/input目录,对每个文件调用openDeviceLocked。
- openDeviceLocked依次执行四个任务。1)创键一个Device对象,填充相关成员。2)将该Device的fd加入到epool。3)EventHub.mDevices.add(device->id, device),把device加入到EventHub.mDevices。4)把Device加入到mOpeningDevices,从而把后绪操作同步化到EventHub::getEvents,在那里将触发类型是DEVICE_ADDED的RawEvent,存储到InputReader.mEventBuffer。
- (processEventsLocked)InputReader开始处理mEventBuffer中的RawEvent。类型是DEVICE_ADDED的,调用addDeviceLocked。它会执行两个任务。1)createDeviceLocked(devicdId, ...)创建InputDevice对象device。2)InputReader.mDevices.add(deviceId, device),把device加入到InputReader.mDevices。
- scanDevicesLocked是创建在一开始就存在于/dev/input下的输入设备,运行时如何增加设备,见步骤6。
- (运行时增加设备)EventHub::getEvents发现mINotifyFd上有可读数据,调用readNotifyLocked。readNotifyLocked从mINotifyFd读数据(struct inotify_event),事件置有IN_CREATE标志,认为/dev/input下创建了新设备,于是调用openDeviceLocked。openDeviceLocked执行过程见步骤3。
mOpeningDevices是个正打开着的设备开环链表,即末尾节点的next是nullptr。后找到的追加到头部,mOpeningDevices指向链表的第一个节点。它的作用不是由它检索出所有设备,而是由它检索出此次loopOnce所有的DEVICE_ADDED事件,这些DEVICE_ADDED事件放入事件集合后,mOpeningDevices将被置nullptr,意味着已没有要发的DEVICE_ADDED事件。
移除设备
- 调用closeDeviceLocked(Device* device),当中三个任务。1)从mEpollFd移除device->id。2)根据device->id从EventHub.mDevices移除Device。3)如果设备正处于mOpeningDevices,从mOpeningDevices删除对应结点即可,否则把device加入到mClosingDevices,从而把后绪操作同步化到EventHub::getEvents,在那里将触发DEVICE_REMOVED的RawEvent,存储到InputReader.mEventBuffer。
- (processEventsLocked)InputReader开始处理mEventBuffer中的RawEvent。类型是DEVICE_REMOVED的,调用removeDeviceLocked。后者会从InputReader.mDevices删除该设备的InputDevice。
- (运行时移除设备)EventHub::getEvents发现mINotifyFd上有可读数据,调用readNotifyLocked。readNotifyLocked从mINotifyFd读数据(struct inotify_event),事件置有IN_CREATE标志,认为/dev/input下移除了设备,于是调用closeDeviceLocked。closeDeviceLocked执行过程见步骤1。
综上所述,表示设备有两个数据结构,1)EventHub中的Device,EventHub.mDevices是存储它的变量。2)InputReader中的InputDevice,InputReader.mDevices是存储它的变量。它们之间联系纽带是Device.id/InputDevice.mId,不论创建还是移除,都要涉及到EventHub、InputReader,逻辑上都是先处理EventHub部分,然后同步化到EventHub::getEvents,让产生RawEvent,InputReader收到RawEvent再做处理。
二、motion事件
<aosp>/frameworks/native/include/android/input.h enum { AMOTION_EVENT_ACTION_MASK = 0xff, AMOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00, AMOTION_EVENT_ACTION_DOWN = 0, AMOTION_EVENT_ACTION_UP = 1, AMOTION_EVENT_ACTION_MOVE = 2, AMOTION_EVENT_ACTION_CANCEL = 3, AMOTION_EVENT_ACTION_OUTSIDE = 4, AMOTION_EVENT_ACTION_POINTER_DOWN = 5, AMOTION_EVENT_ACTION_POINTER_UP = 6, AMOTION_EVENT_ACTION_HOVER_MOVE = 7, AMOTION_EVENT_ACTION_SCROLL = 8, AMOTION_EVENT_ACTION_HOVER_ENTER = 9, AMOTION_EVENT_ACTION_HOVER_EXIT = 10, AMOTION_EVENT_ACTION_BUTTON_PRESS = 11, AMOTION_EVENT_ACTION_BUTTON_RELEASE = 12, };
字面上,AMOTION_EVENT_ACTION_MOVE、AMOTION_EVENT_ACTION_HOVER_MOVE相差一个单词:hover,它指示是否处于盘旋状态。什么是盘旋状态?当前没有按下接触式按钮,接触式按钮包括CursorInputMapper中左键(右键不是接触式按钮),SingleTouchInputMapper中接触式工具BTN_TOUCH。
按下接触式按钮会产生AMOTION_EVENT_ACTION_DOWN、松开则有AMOTION_EVENT_ACTION_UP,这两种事件时actionButton置0。AMOTION_EVENT_ACTION_BUTTON_PRESS、AMOTION_EVENT_ACTION_BUTTON_RELEASE则是按下非接触按钮时产生,包括鼠标右键、TouchInputMapper中的盘旋式工具,这两种事件时必须设对actionButton值,像右键需是AMOTION_EVENT_BUTTON_BACK。以下是CursorInputMapper下,数种操作参应的input_event,以及产生的motion事件。 ==移动(没有按下左键时)。移动了(-2, 6) EV_REL(0x2), REL_X(0x0), -2; EV_REL(0x2), REL_Y(0x1), 6; EV_SYN(0x0), SYN_REPORT(0x0), 0。 如果X或Y有一个没有移动,可以不发该轴。发出的motion事件,action=0x7(AMOTION_EVENT_ACTION_HOVER_MOVE), actionButton=0x0 ==按下左键 EV_MSC(0x4), MSC_SCAN(0x4), 0x90001; EV_KEY(0x1), BTN_MOUSE/BTN_LEFT(0x110), 1; EV_SYN(0x0), SYN_REPORT(0x0), 0。 action=0x0(AMOTION_EVENT_ACTION_DOWN), actionButton=0x0 action=0xb(AMOTION_EVENT_ACTION_BUTTON_PRESS), actionButton=0x1 ==移动(正按下左键时)。移动了(1, -2) EV_REL(0x2), REL_X(0x0), 1; EV_REL(0x2), REL_Y(0x1), -2; EV_SYN(0x0), SYN_REPORT(0x0), 0。 action=0x2(AMOTION_EVENT_ACTION_MOVE), actionButton=0x0 ==松开左键 EV_MSC(0x4), MSC_SCAN(0x4), 0x90001; EV_KEY(0x1), BTN_MOUSE/BTN_LEFT(0x110), 0; EV_SYN(0x0), SYN_REPORT(0x0), 0。 action=0xc(AMOTION_EVENT_ACTION_BUTTON_RELEASE), actionButton=0x1 action=0x1(AMOTION_EVENT_ACTION_UP), actionButton=0x0 action=0x7(AMOTION_EVENT_ACTION_HOVER_MOVE), actionButton=0x0 ==按下右键 EV_MSC(0x4), MSC_SCAN(0x4), 0x90002; EV_KEY(0x1), BTN_RIGHT(0x111), 1; EV_SYN(0x0), SYN_REPORT(0x0), 0。 action=0x7(AMOTION_EVENT_ACTION_HOVER_MOVE), actionButton=0x0 action=0xb(AMOTION_EVENT_ACTION_BUTTON_PRESS), actionButton=0x8 ==松开右键 EV_MSC(0x4), MSC_SCAN(0x4), 0x90002; EV_KEY(0x1), BTN_RIGHT(0x111), 0; EV_SYN(0x0), SYN_REPORT(0x0), 0。 action=0xc(AMOTION_EVENT_ACTION_BUTTON_RELEASE), actionButton=0x8 action=0x7(AMOTION_EVENT_ACTION_HOVER_MOVE), actionButton=0x0 ==滚动(鼠标向鼠标线方向滚动) EV_REL(0x2), REL_WHEEL(0x8), 1; action=0x7(AMOTION_EVENT_ACTION_HOVER_MOVE), actionButton=0x0 action=0x8(AMOTION_EVENT_ACTION_SCROLL), actionButton=0x0 ==滚动(鼠标向商标方向滚动) EV_REL(0x2), REL_WHEEL(0x8), -1; action=0x7(AMOTION_EVENT_ACTION_HOVER_MOVE), actionButton=0x0 action=0x8(AMOTION_EVENT_ACTION_SCROLL), actionButton=0x0
三、TouchInputMapper::sync
3.1 触摸设备分类
参考“触摸设备[1] ”。
触摸屏(touchscreen)是以显示屏为参照的绝对定位设备,其给出的数据是绝对坐标。像ipad、iphone之类的面板都集成有触摸屏。触摸屏在HID设备类当中,是属于touch事件的设备。
触控板(touchpad)是不以显示屏为参照的相对定位设备,其给出的数据是相对坐标数据。比如笔记本上的触摸板。触摸板在HID设备类当中,是属于mouse事件的设备,其工作时通常操作系统上会显示出光标,其工作方式类似于鼠标。
处理触摸设备的输入映射分单点触摸(SingleTouchInputMapper)和多点触摸(MultiTouchInputMapper),它们基类都是TouchInputMapper。
class TouchInputMapper: public InputMapper { CursorButtonAccumulator mCursorButtonAccumulator; CursorScrollAccumulator mCursorScrollAccumulator; TouchButtonAccumulator mTouchButtonAccumulator; uint32_t mSource; <=0x1002(AINPUT_SOURCE_TOUCHSCREEN) DeviceMode mDeviceMode; <=1(DEVICE_MODE_DIRECT) struct Parameters { enum DeviceType { DEVICE_TYPE_TOUCH_SCREEN, DEVICE_TYPE_TOUCH_PAD, DEVICE_TYPE_TOUCH_NAVIGATION, DEVICE_TYPE_POINTER, }; DeviceType deviceType; <=0(DEVICE_TYPE_TOUCH_SCREEN) ... } mParameters; };
Parameters.deviceType、mSource、mDeviceType都和设备分类有关。在次序上先计算Parameters.deviceType,然后mSource、mDeviceMode。如何计算deviceType?1)如果设置了touch.deviceType属性,则将按照指示设置设备类型。2)没有1)时,将通过INPUT_PROP_DIRECT、INPUT_PROP_POINTER、REL_X 或 REL_Y等属性进行判断。对单指触摸屏,往往没有touch.deviceType属性,但上报了INPUT_PROP_DIRECT,得出Parameters.deviceType = DEVICE_TYPE_TOUCH_SCREEN。
Parameters.deviceType有什么作用?主要决定了mDeviceMode、mSource是什么值。是DEVICE_TYPE_TOUCH_SCREEN时,mDeviceMode值是DEVICE_MODE_DIRECT。mDeviceMode又什么用?它决定了如何计算坐标。对DEVICE_MODE_DIRECT时,坐标是由input驱动给出绝对坐标,这坐标有可能不直接对应最后坐标,但也只是初始偏移、缩放等变化,内中没有什么相对坐标计算。设备类型是指针类型(触控板)时(Parameters.deviceType = DEVICE_TYPE_POINTER),mDeviceMode值是DEVICE_MODE_POINTER,uinput驱动报的将是相对坐标,将以着类似处理电脑上鼠标的方法计算对应的屏幕坐标,底下“4.1 由相对坐标计算屏幕坐标”会再说相对坐标。
mSource也用于指示是什么类型的设备,也是由devictType算出。和deviceType、mDeviceMode不同,它会写到发向InputDispatch模块的NotifyMotionArgs.source。对触摸屏,该值往往是AINPUT_SOURCE_TOUCHSCREEN(0x1002)。
3.2 按钮、工具
TouchInputMapper包含两类button,成员mCursorButtonAccumulator中的称为按钮,mTouchButtonAccumulator中的称为工具。
按钮。可供应用来执行其他功能的“可选”控件。触摸设备上的按钮与鼠标按钮类似,主要与“指针式”触摸设备或者触控笔配合使用。若不修改inputfilger源码,只有在有工具按下时,按下按钮才会生效。类似盘旋工具,按钮产生的事件是AMOTION_EVENT_ACTION_BUTTON_PRESS、AMOTION_EVENT_ACTION_BUTTON_RELEASE。
工具(Tool)。工具是用于和触摸设备进行交互的手指、铁笔或其它设备。一个触摸设备可以支多种不同的触摸工具类型。按是否接触,工具分为两类:盘旋式工具和接触式工具,除BTN_TOOL_MOUSE、BTN_TOOL_LENS是接触式工具,其它是盘旋式工具。盘旋式工具产生的事件AMOTION_EVENT_ACTION_BUTTON_PRESS、AMOTION_EVENT_ACTION_BUTTON_RELEASE,接触式则是AMOTION_EVENT_ACTION_DOWN(单指)、AMOTION_EVENT_ACTION_DOWN(单指)、AMOTION_EVENT_ACTION_POINTER_DOWN(多指)、AMOTION_EVENT_ACTION_POINTER_UP(多指)。
3.3 TouchInputMapper::sync流程
图1 TouchInputMapper::sync流程
根据当前正按着的按钮、触摸工具状态,以及坐标信息生成RawState。对坐标,只有有触摸工具正按下时才会生成,否则空。此时生成的坐标称为RawPointerData。接下把RawPointerData格式转成CookedPointerData,这个转换过程称为烹饪。烹饪核心是处理的是坐标,对按钮、触摸工具状态(buttonState、hoveringIdBits/touchingIdBits)基本是原样复制。烹饪后,分发事件。分发时存储事件的数据结构是NotifyMotionArgs,分发后事件将被放入QueuedInputListener.mArgsQueue队列。烹饪前,会清空mCurrentCookedState,分发后会把它赋值给mLastCookedState,也就是说,要是此次生成空坐标,经过此轮sync后,下次sync时的mLastCookedState是空坐标。
注1。outState.rawPointerData成员类型是RawPointerData,它封装了原始坐标数据。
struct RawPointerData { struct Pointer { uint32_t id; int32_t x; int32_t y; int32_t pressure; int32_t touchMajor; int32_t touchMinor; int32_t toolMajor; int32_t toolMinor; int32_t orientation; int32_t distance; int32_t tiltX; int32_t tiltY; int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant bool isHovering; }; // 有效的坐标个数,对单指触摸,只可能是0或1。 uint32_t pointerCount; // 存储着坐标。对单指触摸,只会使用pointers[0]。 Pointer pointers[MAX_POINTERS]; // hoveringIdBits、touchingIdBits有着同样意义,一个是盘旋式工具导致的坐标,一个是接触式工具导致的坐标。当中非0位的个数应该等于pointerCount。既然有了pointerCount,为什么还在xxxIdBits?xxxIdBits中的非0位表示有效坐标在pointers中的位置,当然,计算位置时还须要结合idToIndex。举个例子,xxxIdBits中的bit(0)、bit(1)是1,并且idToIndex[0]=1、idToIndex[1]=2,则表示pointers[0]存储着id=0的坐标,pointers[2]存储着id=1的坐标。对单指触摸,是1的只可能是bit(0)。 BitSet32 hoveringIdBits; BitSet32 touchingIdBits; // id->index映射。id指的是hoveringIdBits/touchingIdBits中的bit(id),index指的是pointers中的索引。在计算时,根据hoveringIdBits/touchingIdBits中的非0位,很容易算出id,然后根据idToIndex由id找到index,从而知道此id对应的坐标数据。对单指触摸,只位置0的会有效,而且对应index是0,即idToIndex[0]=0。 uint32_t idToIndex[MAX_POINTER_ID + 1]; };
经过syncTouch处理,rawPointerData存储着坐标信息,对单指触摸,一旦有坐标,字段pointerCount=1、pointers[0]存储有效数据。补说下,当没有工具(mTouchButtonAccumulator.isToolActive)按下时,该函数是啥也不做。
hoveringIdBits/touchingIdBits类型是BitSet32,表示当中位时用bit(0),而不是bit0,后者一般用于表示内存位。BitSet32希望使用者不要在意内存存储这4个字节时用的是小端序还是大端序,只须知道是个32位的位组,first指的是从n=0开始的bit(0),last指的bit(31)。
注2。对单指触摸,总会执行assignPointerIds。assignPointerIds一进入先会清空hoveringIdBits、touchingIdBits。它是在syncTouch后执行,那是不是把syncTouch生成的这两个变量结果清空了?——是的,assignPointerIds首先会清空next的hoveringIdBits、touchingIdBits,但它接下会重新计算这两个变量进行,重新计算的还有rawPointerData.pointers[i].id、rawPointerData.idToIndex。对单指触摸来说,重新计算不会改变原来值。
注3。processRawTouches为什么不直接把sync中的next变量以参数传下来?——sync中的next指向mRawStatesPending顶部单元,mRawStatesPending可能会存储着历史遗留、需要处理的多个RawState。processRawTouches需要按时间从最早的RawState开始处理。
注4。正如字面看到的,cookAndDispatch有两大任务,第一个是烹饪坐标数据,对应操作cookPointerData,原料是mCurrentRawState.rawPointerData,烧出的菜是mCurrentCookedState.cookedPointerData。第二个是分发motion事件,存储事件的数据结构是NotifyMotionArgs,生成事件后调用getListener()->notifyMotion(&args),即放入QueuedInputListener.mArgsQueue队列。对分发事件,根据mDeviceMode==DEVICE_MODE_POINTER不同,有着非常不一样处理,对触摸屏,mDeviceMode = DEVICE_MODE_DIRECT。
cookPointerData执行坐标数据从RawPointerData到CookedPointerData的格式转换。
void TouchInputMapper::cookPointerData() { mCalibration.sizeCalibration <= SIZE_CALIBRATION_NONE(1)。touchMajor、touchMinor、toolMajor、toolMinor都是0。 mCalibration.pressureCalibration <= PRESSURE_CALIBRATION_NONE(1)。pressure = in.isHovering ? 0 : 1; mHaveTilt <= false mCalibration.orientationCalibration <= ORIENTATION_CALIBRATION_NONE(1)。distance = 0 mCalibration.coverageCalibration <= COVERAGE_CALIBRATION_NONE(1)。rawLeft = rawTop = rawRight = rawBottom = 0 mAffineTransform.applyTo(xTransformed, yTransformed); 出来的xTransformed、yTransformed是原来值in.x、in.y。 mSurfaceOrientation <= DISPLAY_ORIENTATION_0(0); x = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; y = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; }
计算x、y时,为做到xTransformed到x、yTransformed到y直接对应,创建uinput驱动时确保mRawPointerAxes.x.minValue、mRawPointerAxes.y.minValue、mXTranslate、mYTranslate都是0,mXScale、mYScale都是1。。
uinp.absmin[ABS_X] = 0; uinp.absmax[ABS_X] = screenWidth - 1; uinp.absmin[ABS_Y] = 0; uinp.absmax[ABS_Y] = screenHeight - 1;
事件分为五类,分别由五个函数依次去分发,它们是dispatchButtonRelease、dispatchHoverExit、dispatchTouches、dispatchHoverEnterAndMove和dispatchButtonPress。
void TouchInputMapper::cookAndDispatch(nsecs_t when) { ... if (mDeviceMode == DEVICE_MODE_POINTER) { ... } else { ... if (!mCurrentMotionAborted) { dispatchButtonRelease(when, policyFlags); dispatchHoverExit(when, policyFlags); dispatchTouches(when, policyFlags); dispatchHoverEnterAndMove(when, policyFlags); dispatchButtonPress(when, policyFlags); } if (mCurrentCookedState.cookedPointerData.pointerCount == 0) { mCurrentMotionAborted = false; } } .... // Clear some transient state. mCurrentRawState.rawVScroll = 0; mCurrentRawState.rawHScroll = 0; // Copy current touch to last touch in preparation for the next cycle. mLastRawState.copyFrom(mCurrentRawState); mLastCookedState.copyFrom(mCurrentCookedState); }
- dispatchButtonRelease。AMOTION_EVENT_ACTION_BUTTON_RELEASE。判断是否要发的依据变量是mLastCookedState.buttonState、mCurrentCookedState.buttonState。idBits来自mLastCookedState.cookedPointerData中的hoveringIdBits/touchingIdBits。
- dispatchHoverExit。AMOTION_EVENT_ACTION_HOVER_EXIT。若要发送需要满足条件mSentHoverEnter=true
- dispatchTouches。AMOTION_EVENT_ACTION_POINTER_UP(单指触模时AMOTION_EVENT_ACTION_UP)、AMOTION_EVENT_ACTION_MOVE、AMOTION_EVENT_ACTION_POINTER_DOWN(单指触模时AMOTION_EVENT_ACTION_DOWN)。
- dispatchHoverEnterAndMove。AMOTION_EVENT_ACTION_HOVER_ENTER、AMOTION_EVENT_ACTION_HOVER_MOVE。若要发送需要满足条件mCurrentCookedState中“touchingIdBits.isEmpty() && !hoveringIdBits.isEmpty()”。一旦发送了AMOTION_EVENT_ACTION_HOVER_ENTER,会设置mSentHoverEnter=true。什么场景会发送?1)盘旋式工具导致出坐标。2)修改代码,让鼠标右键、滚动可以导致出坐标,此时坐标判断为isHovering==true。
- dispatchButtonPress。AMOTION_EVENT_ACTION_BUTTON_PRESS。判断是否要发的依据变量是mLastCookedState.buttonState、mCurrentCookedState.buttonState。idBits来自mCurrentCookedState.cookedPointerData中的hoveringIdBits/touchingIdBits。
这里说下buttonState中的button是什么。
void TouchInputMapper::sync(nsecs_t when) { next->buttonState = mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState(); } void TouchInputMapper::cookPointerData() { if (mCurrentCookedState.cookedPointerData.pointerCount == 0) { mCurrentCookedState.buttonState = 0; } else { mCurrentCookedState.buttonState = mCurrentRawState.buttonState; } } uint32_t TouchButtonAccumulator::getButtonState() const { uint32_t result = 0; if (mBtnStylus) { result |= AMOTION_EVENT_BUTTON_STYLUS_PRIMARY; } if (mBtnStylus2) { result |= AMOTION_EVENT_BUTTON_STYLUS_SECONDARY; } return result; }
button来自于mTouchButtonAccumulator.getButtonState()、mCursorButtonAccumulator.getButtonState()。CursorButtonAccumulator中的buttonState包括像右键(mBtnRight)。TouchButtonAccumulator中的buttonState就两种:BTN_STYLUS、BTN_STYLUS2,不包括BTN_TOUCH、其它盘旋式工具等。buttonState相关两种事件中,须设对actionButton。
enum { /** primary */ AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0, /** secondary */ AMOTION_EVENT_BUTTON_SECONDARY = 1 << 1, /** tertiary */ AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2, /** back */ AMOTION_EVENT_BUTTON_BACK = 1 << 3, /** forward */ AMOTION_EVENT_BUTTON_FORWARD = 1 << 4, AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5, AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6, }; // AMOTION_EVENT_ACTION_BUTTON_PRESS notifyMotion - eventTime=38394218000, deviceId=6, source=0x1002, policyFlags=0x0, action=0xb, actionButton=0x8, flags=0x0, metaState=0x0, buttonState=0x8,edgeFlags=0x0, xPrecision=1.000000, yPrecision=1.000000, downTime=0 Pointer 0: id=0, toolType=1, x=477.000000, y=659.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000, vscroll=0.000000, hscroll=0.000000 // AMOTION_EVENT_ACTION_BUTTON_RELEASE notifyMotion - eventTime=38442137000, deviceId=6, source=0x1002, policyFlags=0x0, action=0xc, actionButton=0x8, flags=0x0, metaState=0x0, buttonState=0x0,edgeFlags=0x0, xPrecision=1.000000, yPrecision=1.000000, downTime=0 Pointer 0: id=0, toolType=1, x=477.000000, y=659.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000, vscroll=0.000000, hscroll=0.000000
以上两个事件actionButton值是AMOTION_EVENT_BUTTON_BACK(8),操作可能是单击系统‘Back’键,或鼠标右键。
四、CursorInputMapper
由CursorInputMapper触发事件后,屏幕会显示光标图像,TouchInputMapper则不会。
4.1 由相对坐标计算屏幕坐标
CursorInputMapper、TouchInputMapper中mDeviceMode==DEVICE_MODE_POINTER时,uinput上传的是相对坐标,此时须由相对坐标算出屏幕坐标。
在计算相对坐标时,并不是简单的REL_X/REL_Y得出rel。从驱动读出的event_input如何变成真正在用的rel?
- x乘上mXScale,y乘上mYScale。mXScale/mYScale值依赖于configuration中的cursor.mode,"navigation"时是(1.0/6, 1.0/6),其它值("pointer"或"default")时是(1, 1)。这值通常是(1, 1)。
- 旋转。mParameters.orientationAware && mParameters.hasAssociatedDisplay是ture时,根据mOrientation值旋转。一般不会旋转。
- VelocityControl.move。根据使用的速度控制器执行调整。根据鼠标滚动的速度来调整xDelta/yDelta。它是怎么调整呢?config->pointerVelocityControlParameters中的scale字段是基准速度。会使用一个速度跟踪器对象(VelocityTracker),跟踪器又会使用跟踪策略(VelocityTrackerStrategy),这个策略有好多种,由一个字符串时行标识。默认会使用“lsq2”策略:LeastSquaresVelocityTrackerStrategy::getVelocity。
综上所述,如果希望直接做到直接对应,即REL_X是什么值rel就是什么值,可以这么说,基本做不到。
4.2 坐标共享
当有两个input设备在使用CursorInputMapper时,它们会影响同一个坐标系统,也就是说inputfinlger内部对之前的坐标在哪里(mPointerController)是共享的。因为这个共享,建议rdp(远程桌面)不要设为CursorInputMapper。这时如果系统正接着一个在用CursorInputMapper的USB鼠标,移动它后,rdp不会知道有这个移动,内部记着的绝对坐标就过时了,由它算出的相对坐标也就出现错误。那设备连接两个USB鼠标时,为什么不发生错误?USB鼠标驱动不用记住绝对坐标,它们只需算出delta值。不像rdp是先给了绝对坐标,再计算相对坐标。
CursorInputMapper和TouchInputMapper不会共享坐标,即使它们同于一个uinput。举个例子,一个uinput同时支持CursorInputMapper、SingleTouchInputMappe,CursorInputMapper把鼠标移到了(712, 320),然后SingleTouchInputMappe移到(600, 320),此时CursorInputMapper还是认为当前坐标是(712, 320),仍在屏幕(712, 320)处显示光标。由此可以推断,一个设备不要同时支持CursorInputMapper、SingleTouchInputMappe,这只会导致混乱。
五、让SingleTouchInputMapper支持鼠标右键、滚动
<aosp>/frameworks/native/services/inputflinger/InputReader.cpp void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { if (mTouchButtonAccumulator.isToolActive()) { } 改为 void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { bool isCursorActive = (mCursorButtonAccumulator.getButtonState() & AMOTION_EVENT_BUTTON_BACK) || outState->rawVScroll != 0 || outState->rawHScroll != 0; if (mTouchButtonAccumulator.isToolActive() || isCursorActive) { }
让按下鼠标右键、滚动鼠标时,都需要生成有效坐标,此时生成的坐标isHovering=true。如果只是要支持鼠标右键,这一处改动就够了,确切只要“mCursorButtonAccumulator.getButtonState() & AMOTION_EVENT_BUTTON_BACK”。右键将产生的事件将由cookAndDispatch中的dispatchButtonRelease、dispatchButtonPress分发出去。
如果要支持鼠标滚动,还要修改代码让分发AMOTION_EVENT_ACTION_SCROLL事件。
void TouchInputMapper::cookAndDispatch(nsecs_t when) { dispatchHoverEnterAndMove(when, policyFlags); dispatchButtonPress(when, policyFlags); } 改为 void TouchInputMapper::cookAndDispatch(nsecs_t when) { dispatchHoverEnterAndMove(when, policyFlags); dispatchButtonPress(when, policyFlags); dispatchWheelScroll(when, policyFlags); }
五个分发函数再加一个分发:dispatchWheelScroll。
void TouchInputMapper::dispatchWheelScroll(nsecs_t when, uint32_t policyFlags) { if (mCurrentRawState.rawVScroll == 0 && mCurrentRawState.rawHScroll == 0) { return; } const int32_t metaState = getContext()->getGlobalMetaState(); nsecs_t downTime = mDownTime; // when float vscroll = mCurrentRawState.rawVScroll; float hscroll = mCurrentRawState.rawHScroll; // mWheelYVelocityControl.move(when, NULL, &vscroll); // mWheelXVelocityControl.move(when, &hscroll, NULL); // Send scroll. PointerCoords pointerCoords; pointerCoords.copyFrom(mCurrentCookedState.cookedPointerData.pointerCoords[0]); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, mCurrentRawState.buttonState, 0, mViewport.displayId, 1, mCurrentCookedState.cookedPointerData.pointerProperties, &pointerCoords, mOrientedXPrecision, mOrientedYPrecision, downTime); getListener()->notifyMotion(&args); }
由于dispatchWheelScroll之前有五个dispatch,支持滚动后,一次滚动时发送的不仅仅是AMOTION_EVENT_ACTION_SCROLL事件。
rawVscroll=1(鼠标向鼠标线方向滚动)。 AMOTION_EVENT_ACTION_HOVER_MOVE(7),AMOTION_EVENT_ACTION_SCROLL(8) uinput==> Input event: device=6 type=0x0002 code=0x0008 value=0x00000001 when=209765951000 ------------------------- notifyMotion - eventTime=209765951000, deviceId=6, source=0x1002, policyFlags=0x0, action=0x7, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0,edgeFlags=0x0, xPrecision=1.000000, yPrecision=1.000000, downTime=59851599000 Pointer 0: id=0, toolType=1, x=1010.000000, y=762.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000, vscroll=0.000000, hscroll=0.000000 notifyMotion - eventTime=209765951000, deviceId=6, source=0x1002, policyFlags=0x0, action=0x8, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0,edgeFlags=0x0, xPrecision=1.000000, yPrecision=1.000000, downTime=59851599000 Pointer 0: id=0, toolType=1, x=1010.000000, y=762.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000, vscroll=1.000000, hscroll=0.000000 rawVscroll=-1(鼠标向商标方向滚动) AMOTION_EVENT_ACTION_HOVER_MOVE(7),AMOTION_EVENT_ACTION_SCROLL(8) uinput==> Input event: device=6 type=0x0002 code=0x0008 value=0xffffffff when=366048822000 ------------------- notifyMotion - eventTime=366048822000, deviceId=6, source=0x1002, policyFlags=0x0, action=0x7, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0,edgeFlags=0x0, xPrecision=1.000000, yPrecision=1.000000, downTime=59851599000 Pointer 0: id=0, toolType=1, x=932.000000, y=465.000000, pressure=0.000000, size=0.000000, touchMajor=0.000000, touchMinor=0.000000, toolMajor=0.000000, toolMinor=0.000000, orientation=0.000000, vscroll=0.000000, hscroll=0.000000 notifyMotion - eventTime=366048822000, deviceId=6, source=0x1002, policyFlags=0x0, action=0x8, actionButton=0x0, flags=0x0, metaState=0x0, buttonState=0x0,edgeFlags=0x0, xPrecision=1.000000, yPrecision=1.000000, downTime=59851599000 Pointer 0: id=0, toolType=1, x=932.000000, y=465.000000, pressure=0.000000, size=0.00000
# Android 环境安装
sudo apt-get install -qq git gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig libssl-dev bc cpio lz4 file device-tree-compiler rsync openssl p7zip-full