目标
本文分析的案例:手指在触摸屏上按下,移动,抬起。
SLOT 协议
驱动使用 A/B 协议来上报触摸屏事件,B 协议,通常也称之为 slot 协议,先简单介绍下这个协议是如何上报数据的
首先通过 adb shell getevent -l 监测驱动上报的数据
当手指按下时,会有如下数据
//type //code //value
EV_ABS ABS_MT_SLOT 00000000
EV_ABS ABS_MT_TRACKING_ID 00000000
EV_ABS ABS_MT_POSITION_X 000002ea
EV_ABS ABS_MT_POSITION_Y 00000534
EV_SYN SYN_REPORT 00000000
解释下这些数据的含义
- ABS_MT_SLOT 代表这一组数据,由哪个 slot 上报。
- ABS_MT_TRACKING_ID 代表这一组数据,属于哪个手指。
- ABS_MT_POSITION_X 或 ABS_MT_POSITION_Y,代表手指触摸的 x, y 坐标。
- SYN_REPORT ,它不属于触摸数据,它用来提示 Input 系统同步刚才发送的一组数据。
当手指移动时,会有如下数据
EV_ABS ABS_MT_POSITION_X 000002ec
EV_ABS ABS_MT_POSITION_Y 00000526
EV_SYN SYN_REPORT 00000000
这里只有坐标数据,并没有 slot 和 tracking id 数据,而这些缺失的数据,默认沿用上一次的数据。
当手指从屏幕上抬起时,会有如下数据
EV_ABS ABS_MT_TRACKING_ID ffffffff
EV_SYN SYN_REPORT 00000000
没有 slot 数据,同样沿用上一次的 slot 数据。tracking id 的值为 -1,代表手指抬起。
InputReader 处理触摸事件
void InputReader::loopOnce() {
// ...
std::list<NotifyArgs> notifyArgs;
// ...
// 1. 从 EventHub 获取到事件
std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);
{ // acquire lock
// ...
// 2. 处理事件
if (!events.empty()) {
notifyArgs += processEventsLocked(events.data(), events.size());
}
// ...
} // release lock
// ...
// 3. 把事件保存到 mQueuedListener,并刷新队列,把所有事件发送给下一环
notifyAll(std::move(notifyArgs));
mQueuedListener.flush();
}
std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
std::list<NotifyArgs> out;
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type;
size_t batchSize = 1;
if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
// 获取同一个设备的事件数量
int32_t deviceId = rawEvent->deviceId;
while (batchSize < count) {
if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
rawEvent[batchSize].deviceId != deviceId) {
break;
}
batchSize += 1;
}
if (debugRawEvents()) {
ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
}
// 批量处理同一个设备的事件
out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
}
count -= batchSize;
rawEvent += batchSize;
}
return out;
}
std::list<NotifyArgs> InputReader::processEventsForDeviceLocked(int32_t eventHubId,
const RawEvent* rawEvents,
size_t count) {
auto deviceIt = mDevices.find(eventHubId);
if (deviceIt == mDevices.end()) {
ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
return {};
}
std::shared_ptr<InputDevice>& device = deviceIt->second;
if (device->isIgnored()) {
// ALOGD("Discarding event for ignored deviceId %d.", deviceId);
return {};
}
// 交给 InputDevice 处理
return device->process(rawEvents, count);
}
在 InputReader 中,InputDevice 代表一个输入设备。InputReader 把属于同一个设备的数据,批量交给 InputDevice 处理
std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t count) {
std::list<NotifyArgs> out;
// 遍历所有待处于 raw events
for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
if (debugRawEvents()) {
const auto [type, code, value] =
InputEventLookup::getLinuxEvdevLabel(rawEvent->type, rawEvent->code,
rawEvent->value);
ALOGD("Input event: eventHubDevice=%d type=%s code=%s value=%s when=%" PRId64,
rawEvent->deviceId, type.c_str(), code.c_str(), value.c_str(), rawEvent->when);
}
if (mDropUntilNextSync) {
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
} else {
// 把 raw event 逐个交给所有 InputMapper 处理,得到一些加工后的事件
for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
out += mapper.process(rawEvent);
});
}
--count;
}
// 返回经过 InputMapper 加工后的事件
return out;
}
InputDevice 逐个地把 raw event 交给所有的 InputMapper 处理,InputMapper 处理完了之后,会把 raw event 的数据包装到新的事件中,然后返回。
MultiInputMapper 处理触摸事件
对于支持多点触摸的设备,InputMapper 为 MultiTouchInputMapper,来看下它如何处理触摸事件
std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) {
// 父类 TouchInputMapper 主要处理同步事件
std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
// 累加器来触摸事件数据
mMultiTouchMotionAccumulator.process(rawEvent);
return out;
}
MultiTouchInputMapper 使用父类处理同步事件,使用自己的累加器处理真正的触摸事件数据。
累加器处理触摸事件
void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_ABS) {
bool newSlot = false;
if (mUsingSlotsProtocol) {
// 如果有 slot 数据,就获取 slot 索引,否则沿用上一次的 slot 索引
if (rawEvent->code == ABS_MT_SLOT) {
mCurrentSlot = rawEvent->value;
newSlot = true;
}
} else if (mCurrentSlot < 0) {
}
if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) {
} else {
Slot& slot = mSlots[mCurrentSlot];
if (!mUsingSlotsProtocol) {
}
switch (rawEvent->code) {
case ABS_MT_POSITION_X:
slot.mAbsMtPositionX = rawEvent->value;
warnIfNotInUse(*rawEvent, slot);
break;
case ABS_MT_POSITION_Y:
slot.mAbsMtPositionY = rawEvent->value;
warnIfNotInUse(*rawEvent, slot);
break;
// ...
}
}
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
}
}
很简单,根据 slot 索引,把触摸事件的数据,填充到数组 mSlots 对应的元素中。
举个例子,当手指按下时会有如下数据
EV_ABS ABS_MT_SLOT 00000000
EV_ABS ABS_MT_TRACKING_ID 00000000
EV_ABS ABS_MT_POSITION_X 000002ea
EV_ABS ABS_MT_POSITION_Y 00000534
EV_SYN SYN_REPORT 00000000
就是把第2、3、4行的数据,保存到 mSlot[0] 。
TouchInputMapper 处理同步事件
MultiTouchInputMapper 利用父类 TouchInputMapper 来处理同步事件
std::list<NotifyArgs> TouchInputMapper::process(const RawEvent* rawEvent) {
// ...
std::list<NotifyArgs> out;
// 处理同步事件
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
out += sync(rawEvent->when, rawEvent->readTime);
}
return out;
}
std::list<NotifyArgs> TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
std::list<NotifyArgs> out;
if (mDeviceMode == DeviceMode::DISABLED) {
// Only save the last pending state when the device is disabled.
mRawStatesPending.clear();
}
// 1.添加一个空元素
mRawStatesPending.emplace_back();
// 获取这个空元素,并填充数据
RawState& next = mRawStatesPending.back();
next.clear();
next.when = when;
next.readTime = readTime;
// ...
// 2.同步触摸数据,保存到 next 中
// 由子类实现
syncTouch(when, &next);
// The last RawState is the actually second to last, since we just added a new state
const RawState& last =
mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1];
// ...
// 同步触摸数据的过程中,如果没有手指id,那么沿用上一次事件的手指 id
if (!mHavePointerIds) {
assignPointerIds(last, next);
}
// ...
// 3.处理同步过来的数据
out += processRawTouches(/*timeout=*/false);
return out;
}
TouchInputMapper 处理同步事件
- mRawStatesPending 队尾添加一个空元素。
- 把子类获取的数据,同步到 mRawStatesPending 最后一个元素中。
- 处理 mRawStatesPending 中同步过来的数据。
同步数据
MultiTouchInputMapper 同步触摸数据
void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
size_t outCount = 0;
BitSet32 newPointerIdBits;
mHavePointerIds = true;
// 遍历所有 Slot
for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
// 获取 Slot
// Slot 保存的是 raw event 的数据
const MultiTouchMotionAccumulator::Slot& inSlot =
mMultiTouchMotionAccumulator.getSlot(inIndex);
// ...
// RawState::rawPointerData 不仅保存触摸事件的所有元数据,还会额外解析其他数据保存
RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount];
outPointer.x = inSlot.getX();
outPointer.y = inSlot.getY();
// ...
// Assign pointer id using tracking id if available.
if (mHavePointerIds) {
// 获取 tracking id,有些 raw event 是不带 tracking id,沿用上一次 raw event 的 tracking id
int32_t trackingId = inSlot.getTrackingId();
int32_t id = -1;
if (trackingId >= 0) { // 触摸事件中,指定了 tracking id
for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) {
}
if (id < 0 && !mPointerIdBits.isFull()) {
// 创建一个手指id
id = mPointerIdBits.markFirstUnmarkedBit();
// id -> trakcing id
mPointerTrackingIdMap[id] = trackingId;
}
}
if (id < 0) {
// 即使触摸事件没有指定 tracking id,在后面的流程中,会把前一个事件的手指 id 赋给它
mHavePointerIds = false;
outState->rawPointerData.clearIdBits();
newPointerIdBits.clear();
} else { // 触摸事件的数据中,指定了 tracking id
// id 是数组 mPointerTrackingIdMap 的索引
outPointer.id = id;
// 保存 id -> index 的关系
// id 是数组 mPointerTrackingIdMap 的索引
// index 是数组 RawState::rawPointerData.pointers 的索引
outState->rawPointerData.idToIndex[id] = outCount;
outState->rawPointerData.markIdBit(id, isHovering);
newPointerIdBits.markBit(id);
}
}
outCount += 1;
}
// 保存手指的数量
outState->rawPointerData.pointerCount = outCount;
// mPointerIdBits 保存所有手指的索引,这个索引是数组 mPointerTrackingIdMap 的索引
mPointerIdBits = newPointerIdBits;
// 对于使用 slot 协议,累加器的完成同步,没有做什么
mMultiTouchMotionAccumulator.finishSync();
}
这些代码看起来很头晕,简单来说,做了三件事
- 把累加器中数组 mSlot 中的数据,同步到 RawState::rawPointerData,当然,还会额外解析一些数据,例如手指数量。
- MultiTouchInputMapper 为 tracking id 生成一个新的 id。在实际使用中,是用这个新 id 代表手指 id 的。
- MultiTouchInputMapper 使用 mPointerIdBits 保存所有手指的 id。、
来看下同步数据所使用的数据结构
struct RawPointerData {
// 保存触摸数据,例如 x, y 坐标
struct Pointer {
// id 是 MultiTouchInputMapper 为手指重新生成的 id,并不是实际的 tracking id
uint32_t id{0xFFFFFFFF};
int32_t x{};
int32_t y{};
// ...
};
// 手指的数量
uint32_t pointerCount{};
// 手指的触摸数据
std::array<Pointer, MAX_POINTERS> pointers{};
// id -> index 映射关系
// id 是 MultiTouchInputMapper 为手指重新生成的 id,并不是实际的 tracking id
// index 是数组 pointers 的索引
// 这样可以通过手指 id ,找到对应手指的触摸数据
IdToIndexArray idToIndex{};
};
处理同步后的数据
数据现在都同步到了 TouchInputMapper::mRawStatesPending 中,现在来处理它
std::list<NotifyArgs> TouchInputMapper::processRawTouches(bool timeout) {
std::list<NotifyArgs> out;
// ...
const size_t N = mRawStatesPending.size();
size_t count;
// 逐个处理 mRawStatesPending 中的数据
for (count = 0; count < N; count++) {
const RawState& next = mRawStatesPending[count];
// ...
// mCurrentRawState 保存当前正在处理的事件的数据
mCurrentRawState = next;
// ...
// 加工和分发
out += cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime);
}
if (count != 0) {
mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count);
}
// ...
return out;
}
std::list<NotifyArgs> TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
std::list<NotifyArgs> out;
// mCurrentCookedState 保存加工后的事件的数据
mCurrentCookedState.clear();
// ...
// Cook pointer data. This call populates the mCurrentCookedState.cookedPointerData structure
// with cooked pointer data that has the same ids and indices as the raw data.
// The following code can use either the raw or cooked data, as needed.
// 1. 加工触摸事件
cookPointerData();
// ...
if (mDeviceMode == DeviceMode::POINTER) {
} else {
if (!mCurrentMotionAborted) {
// ...
// 2. 创建用于分发的事件
out += dispatchTouches(when, readTime, policyFlags);
// ...
}
if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
mCurrentMotionAborted = false;
}
}
// ...
// Copy current touch to last touch in preparation for the next cycle.
// 3.保存上次处理过的事件,以及加工后的事件,用于在下次循环中使用
mLastRawState = mCurrentRawState;
mLastCookedState = mCurrentCookedState;
return out;
}
处理同步后的数据,就是逐个处理 mRawStatesPending 中保存的数据
- 对事件进行加工,例如把触摸屏坐标转换为显示屏坐标。
- 创建用于分发的事件。这一步是把 raw event ,转换为 ACTION DOWN, ACTION MOVE ,ACTION UP 这样的事件。
- 用 mLastRawState 保存上一次处理过的 raw event 数据,用 mLastCookedState 保存上一次加工后的事件的数据。
加工触摸事件
void TouchInputMapper::cookPointerData() {
// mCurrentRawState 是当前正在处理的”同步后的事件”
uint32_t currentPointerCount = mCurrentRawState.rawPointerData.pointerCount;
// 加工后的事件,保存到 mCurrentCookedState
mCurrentCookedState.cookedPointerData.clear();
mCurrentCookedState.cookedPointerData.pointerCount = currentPointerCount;
mCurrentCookedState.cookedPointerData.touchingIdBits =
mCurrentRawState.rawPointerData.touchingIdBits;
// ...
// Walk through the the active pointers and map device coordinates onto
// display coordinates and adjust for display orientation.
for (uint32_t i = 0; i < currentPointerCount; i++) {
const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];
// ...
vec2 transformed = {in.x, in.y};
mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/);
// 把触摸屏坐标,转换成显示屏坐标
transformed = mRawToDisplay.transform(transformed);
// Write output coords.
PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
out.clear();
out.setAxisValue(AMOTION_EVENT_AXIS_X, transformed.x);
out.setAxisValue(AMOTION_EVENT_AXIS_Y, transformed.y);
// ...
uint32_t id = in.id;
// ...
// Write output properties.
PointerProperties& properties = mCurrentCookedState.cookedPointerData.pointerProperties[i];
properties.clear();
properties.id = id;
properties.toolType = in.toolType;
// Write id index and mark id as valid.
mCurrentCookedState.cookedPointerData.idToIndex[id] = i;
mCurrentCookedState.cookedPointerData.validIdBits.markBit(id);
}
}
所谓的加工,除了把 raw event 的数据转移到 mCurrentCookedState 中,还额外解析并保存了一些数据,例如,把触摸屏的坐标转换成显示屏的坐标。
生成用于分发的事件
std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime,
uint32_t policyFlags) {
std::list<NotifyArgs> out;
// 当前 cook 事件中的手指 id 索引
BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
// 前一个 cook 事件的手指 id 索引
BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentCookedState.buttonState;
if (currentIdBits == lastIdBits) { // 前后两次事件的 id 索引相等,表示是 MOVE 事件
if (!currentIdBits.isEmpty()) {
// No pointer id changes so this is a move event.
// The listener takes care of batching moves so we don't have to deal with that here.
// 保存 move 事件
out.push_back(
dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE,
0, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits,
-1, mOrientedXPrecision, mOrientedYPrecision, mDownTime,
MotionClassification::NONE));
}
} else {// 前后两次事件的 id 索引不相等
// There may be pointers going up and pointers going down and pointers moving
// all at the same time.
// 对比前后两次事件的手指 id 索引,可以推测出是否有 down,move,up 事件
BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
BitSet32 dispatchedIdBits(lastIdBits.value);
// ...
// Dispatch pointer up events.
while (!upIdBits.isEmpty()) { // 有手指抬起
uint32_t upId = upIdBits.clearFirstMarkedBit();
bool isCanceled = mCurrentCookedState.cookedPointerData.canceledIdBits.hasBit(upId);
if (isCanceled) {
ALOGI("Canceling pointer %d for the palm event was detected.", upId);
}
// 保存 up 事件
out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
AMOTION_EVENT_ACTION_POINTER_UP, 0,
isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState,
buttonState, 0,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex,
dispatchedIdBits, upId, mOrientedXPrecision,
mOrientedYPrecision, mDownTime,
MotionClassification::NONE));
dispatchedIdBits.clearBit(upId);
mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId);
}
// ...
// Dispatch pointer down events using the new pointer locations.
while (!downIdBits.isEmpty()) { // 有手指按下
uint32_t downId = downIdBits.clearFirstMarkedBit();
dispatchedIdBits.markBit(downId);
if (dispatchedIdBits.count() == 1) {
// First pointer is going down. Set down time.
mDownTime = when;
}
// 保存 down 事件
out.push_back(
dispatchMotion(when, readTime, policyFlags, mSource,
AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState,
0, mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
dispatchedIdBits, downId, mOrientedXPrecision,
mOrientedYPrecision, mDownTime, MotionClassification::NONE));
}
}
return out;
}
生成用于分发的事件的原理,其实就是对比前后两次数据,创建 ACTION DOWN, MOVE, UP 事件。例如,前后两次事件的手指相等,那么这次事件肯定是 MOVE 事件。
创建用于分发的事件,这里很有考究,来细致讲下
NotifyMotionArgs TouchInputMapper::dispatchMotion(
nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
float yPrecision, nsecs_t downTime, MotionClassification classification) {
PointerCoords pointerCoords[MAX_POINTERS];
PointerProperties pointerProperties[MAX_POINTERS];
uint32_t pointerCount = 0;
// 1,
while (!idBits.isEmpty()) {
uint32_t id = idBits.clearFirstMarkedBit();
uint32_t index = idToIndex[id];
// 保存id
pointerProperties[pointerCount].copyFrom(properties[index]);
// 保存触摸事件的信息,例如x,y坐标
pointerCoords[pointerCount].copyFrom(coords[index]);
// action 的前8位,是 数组pointerCoords 和 数组pointerProperties 的 index
if (changedId >= 0 && id == uint32_t(changedId)) {
action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
}
pointerCount += 1;
}
ALOG_ASSERT(pointerCount != 0);
// 2.
if (changedId >= 0 && pointerCount == 1) { // 只有一个手指
// Replace initial down and final up action.
// We can compare the action without masking off the changed pointer index
// because we know the index is 0.
if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) {
// AMOTION_EVENT_ACTION_POINTER_DOWN 转换为 AMOTION_EVENT_ACTION_DOWN
action = AMOTION_EVENT_ACTION_DOWN;
} else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
if ((flags & AMOTION_EVENT_FLAG_CANCELED) != 0) {
action = AMOTION_EVENT_ACTION_CANCEL;
} else {
// AMOTION_EVENT_ACTION_POINTER_UP 转化为 AMOTION_EVENT_ACTION_UP
action = AMOTION_EVENT_ACTION_UP;
}
} else {
// Can't happen.
ALOG_ASSERT(false);
}
}
// ...
// 3 创建 NotifyMotionArgs
return NotifyMotionArgs(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
policyFlags, action, actionButton, flags, metaState, buttonState,
classification, edgeFlags, pointerCount, pointerProperties,
pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
downTime, std::move(frames));
}
第一步,在 action 的前8位,混入了 index(即 pointerCount),这个 index 是 数组pointerProperties 和 数组pointerCoords 的索引。这样有什么好处呢? 首先可以通过 action 的前八位,解析出 index,然后可以通过 index ,从 pointerProperties 中获取手指 id,或者从 pointerCoords 获取 x,y 坐标。
默认情况下,MOVE 事件的 action 统一为 AMOTION_EVENT_ACTION_POINTER_DOWN,up 事件的 action 统一为 AMOTION_EVENT_ACTION_POINTER_UP。但是,如果只有一个手指,那么会把 AMOTION_EVENT_ACTION_POINTER_DOWN 转换为 AMOTION_EVENT_ACTION_DOWN,把 AMOTION_EVENT_ACTION_POINTER_UP 转换为 AMOTION_EVENT_ACTION_UP。这就是第2步所做的。
app 开发时,使用的多点触摸的原理,就在这儿,请细品。
第三步,创建用于分发的事件 NotifyMotionArgs。这就是 InputReader 处理触摸事件最终的结果。