kOS(13):inputflinger—InputReader线程

一、InputReader线程

 

  1. getEvents。通过EventHub(监听目录/dev/input)读取input_event格式的事件放入mEventBuffer,mEventBuffer是一个大小为256的RawEvent数组。
  2. processEventsLocked。对事件进行加工,转换RawEvent -> NotifyKeyArgs/NotifyMotionArgs(基类NotifyArgs),将NotifyArgs放在QueuedInputListener的mArgsQueue队列。
  3. 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目录下设备文件的创建、删除。以下是创建设备的步骤。

  1. 创建监听“/dev/input”目录的INotify文件描述符mINotifyFd,把它加入epool描述符mEpollFd。
  2. 执行scanDevicesLocked。它会枚举/dev/input目录,对每个文件调用openDeviceLocked。
  3. 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。
  4.  (processEventsLocked)InputReader开始处理mEventBuffer中的RawEvent。类型是DEVICE_ADDED的,调用addDeviceLocked。它会执行两个任务。1)createDeviceLocked(devicdId, ...)创建InputDevice对象device。2)InputReader.mDevices.add(deviceId, device),把device加入到InputReader.mDevices。
  5. scanDevicesLocked是创建在一开始就存在于/dev/input下的输入设备,运行时如何增加设备,见步骤6。
  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事件。

移除设备

  1. 调用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。
  2. (processEventsLocked)InputReader开始处理mEventBuffer中的RawEvent。类型是DEVICE_REMOVED的,调用removeDeviceLocked。后者会从InputReader.mDevices删除该设备的InputDevice。
  3. (运行时移除设备)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?

  1. x乘上mXScale,y乘上mYScale。mXScale/mYScale值依赖于configuration中的cursor.mode,"navigation"时是(1.0/6, 1.0/6),其它值("pointer"或"default")时是(1, 1)。这值通常是(1, 1)。
  2. 旋转。mParameters.orientationAware && mParameters.hasAssociatedDisplay是ture时,根据mOrientation值旋转。一般不会旋转。
  3. 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
  1. 触摸设备 https://source.android.google.cn/devices/input/touch-devices

 

# 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

全部评论: 0

    写评论: