- 我们已经知道在Input启动过程创建了InputReader线程。InputReader线程创建后会持续调用loopOnce。
kotlin
// frameworks/native/services/inputflinger/reader/InputReader.cpp
status_t InputReader::start() {
if (mThread) {
return ALREADY_EXISTS;
}
mThread = std::make_unique<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}
- 这里会调用
mEventHub->getEvents
从设备节点中获取一批RawEvent(原事件),并调用processEventsLocked
将RawEvents封装成NotifyArgs,最后将NotifyArgs发送给InputDispacher。
scss
// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
std::list<NotifyArgs> notifyArgs;
{ // acquire lock
std::scoped_lock _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
// 如果需要,将刷新设备的配置
auto changes = mConfigurationChangesToRefresh;
if (changes.any()) {
mConfigurationChangesToRefresh.clear();
timeoutMillis = 0;
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
// 通过EventHub从设备节点中读取RawEvent
std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);
{ // acquire lock
std::scoped_lock _l(mLock);
mReaderIsAliveCondition.notify_all();
if (!events.empty()) {
// 将RawEvents封装成PendingArgs
mPendingArgs += processEventsLocked(events.data(), events.size());
}
if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
if (debugRawEvents()) {
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
}
mNextTimeout = LLONG_MAX;
mPendingArgs += timeoutExpiredLocked(now);
}
}
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
inputDevices = getInputDevicesLocked();
mPendingArgs.emplace_back(
NotifyInputDevicesChangedArgs{mContext.getNextId(), inputDevices});
}
std::swap(notifyArgs, mPendingArgs);
for (const NotifyArgs& args : notifyArgs) {
mLastUsedDeviceId = getDeviceIdOfNewGesture(args).value_or(mLastUsedDeviceId);
}
} // release lock
// 通知到监听器,在创建InputReader时有将InputDispacher封装为监听器传给InputReader,所以这里可以把事件发给InputReader。
for (const NotifyArgs& args : notifyArgs) {
mNextListener.notify(args);
}
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
for (const auto& args : notifyArgs) {
const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
}
}
}
- getEvents负责从设备节点中读取RawEvent。
scss
// frameworks/native/services/inputflinger/reader/EventHub.cpp
std::vector<RawEvent> EventHub::getEvents(int timeoutMillis) {
std::scoped_lock _l(mLock);
std::array<input_event, EVENT_BUFFER_SIZE> readBuffer;
std::vector<RawEvent> events;
bool awoken = false;
for (;;) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
// 如果需要重新打开设备,先将设备关掉
if (mNeedToReopenDevices) {
mNeedToReopenDevices = false;
ALOGI("Reopening all input devices due to a configuration change.");
closeAllDevicesLocked();
mNeedToScanDevices = true;
break; // return to the caller before we actually rescan
}
// 报告被删除的设备
for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
std::unique_ptr<Device> device = std::move(*it);
ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str());
const int32_t deviceId = (device->id == mBuiltInKeyboardId)
? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID
: device->id;
events.push_back({
.when = now,
.deviceId = deviceId,
.type = DEVICE_REMOVED,
});
it = mClosingDevices.erase(it);
mNeedToSendFinishedDeviceScan = true;
if (events.size() == EVENT_BUFFER_SIZE) {
break;
}
}
// 如果需要就扫描设备
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
// 报告有设备被添加
while (!mOpeningDevices.empty()) {
std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
mOpeningDevices.pop_back();
ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());
const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
events.push_back({
.when = now,
.deviceId = deviceId,
.type = DEVICE_ADDED,
});
// Try to find a matching video device by comparing device names
for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end();
it++) {
std::unique_ptr<TouchVideoDevice>& videoDevice = *it;
if (tryAddVideoDeviceLocked(*device, videoDevice)) {
// videoDevice was transferred to 'device'
it = mUnattachedVideoDevices.erase(it);
break;
}
}
auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
if (!inserted) {
ALOGW("Device id %d exists, replaced.", device->id);
}
mNeedToSendFinishedDeviceScan = true;
if (events.size() == EVENT_BUFFER_SIZE) {
break;
}
}
// 报告设备扫描完成
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
events.push_back({
.when = now,
.type = FINISHED_DEVICE_SCAN,
});
if (events.size() == EVENT_BUFFER_SIZE) {
break;
}
}
// 获取输入事件
bool deviceChanged = false;
while (mPendingEventIndex < mPendingEventCount) {
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
if (eventItem.data.fd == mINotifyFd) {
if (eventItem.events & EPOLLIN) {
mPendingINotify = true;
} else {
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
}
continue;
}
if (eventItem.data.fd == mWakeReadPipeFd) {
if (eventItem.events & EPOLLIN) {
ALOGV("awoken after wake()");
awoken = true;
char wakeReadBuffer[16];
ssize_t nRead;
do {
nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer));
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer));
} else {
ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
eventItem.events);
}
continue;
}
Device* device = getDeviceByFdLocked(eventItem.data.fd);
if (device == nullptr) {
ALOGE("Received unexpected epoll event 0x%08x for unknown fd %d.", eventItem.events,
eventItem.data.fd);
ALOG_ASSERT(!DEBUG);
continue;
}
if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) {
if (eventItem.events & EPOLLIN) {
size_t numFrames = device->videoDevice->readAndQueueFrames();
if (numFrames == 0) {
ALOGE("Received epoll event for video device %s, but could not read frame",
device->videoDevice->getName().c_str());
}
} else if (eventItem.events & EPOLLHUP) {
// TODO(b/121395353) - consider adding EPOLLRDHUP
ALOGI("Removing video device %s due to epoll hang-up event.",
device->videoDevice->getName().c_str());
unregisterVideoDeviceFromEpollLocked(*device->videoDevice);
device->videoDevice = nullptr;
} else {
ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
device->videoDevice->getName().c_str());
ALOG_ASSERT(!DEBUG);
}
continue;
}
// This must be an input event
if (eventItem.events & EPOLLIN) {
int32_t readSize =
read(device->fd, readBuffer.data(),
sizeof(decltype(readBuffer)::value_type) * readBuffer.size());
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
// Device was removed before INotify noticed.
ALOGW("could not get event, removed? (fd: %d size: %" PRId32
" capacity: %zu errno: %d)\n",
device->fd, readSize, readBuffer.size(), errno);
deviceChanged = true;
closeDeviceLocked(*device);
} else if (readSize < 0) {
if (errno != EAGAIN && errno != EINTR) {
ALOGW("could not get event (errno=%d)", errno);
}
} else if ((readSize % sizeof(struct input_event)) != 0) {
ALOGE("could not get event (wrong size: %d)", readSize);
} else {
const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
const size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
struct input_event& iev = readBuffer[i];
device->trackInputEvent(iev);
events.push_back({
.when = processEventTimestamp(iev),
.readTime = systemTime(SYSTEM_TIME_MONOTONIC),
.deviceId = deviceId,
.type = iev.type,
.code = iev.code,
.value = iev.value,
});
}
if (events.size() >= EVENT_BUFFER_SIZE) {
// The result buffer is full. Reset the pending event index
// so we will try to read the device again on the next iteration.
mPendingEventIndex -= 1;
break;
}
}
} else if (eventItem.events & EPOLLHUP) {
ALOGI("Removing device %s due to epoll hang-up event.",
device->identifier.name.c_str());
deviceChanged = true;
closeDeviceLocked(*device);
} else {
ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
device->identifier.name.c_str());
}
}
// readNotify() will modify the list of devices so this must be done after
// processing all other events to ensure that we read all remaining events
// before closing the devices.
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
mPendingINotify = false;
const auto res = readNotifyLocked();
if (!res.ok()) {
ALOGW("Failed to read from inotify: %s", res.error().message().c_str());
}
deviceChanged = true;
}
// Report added or removed devices immediately.
if (deviceChanged) {
continue;
}
// Return now if we have collected any events or if we were explicitly awoken.
if (!events.empty() || awoken) {
break;
}
// Poll for events.
// When a device driver has pending (unread) events, it acquires
// a kernel wake lock. Once the last pending event has been read, the device
// driver will release the kernel wake lock, but the epoll will hold the wakelock,
// since we are using EPOLLWAKEUP. The wakelock is released by the epoll when epoll_wait
// is called again for the same fd that produced the event.
// Thus the system can only sleep if there are no events pending or
// currently being processed.
//
// The timeout is advisory only. If the device is asleep, it will not wake just to
// service the timeout.
mPendingEventIndex = 0;
mLock.unlock(); // release lock before poll
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mLock.lock(); // reacquire lock after poll
if (pollResult == 0) {
// Timed out.
mPendingEventCount = 0;
break;
}
if (pollResult < 0) {
// An error occurred.
mPendingEventCount = 0;
// Sleep after errors to avoid locking up the system.
// Hopefully the error is transient.
if (errno != EINTR) {
ALOGW("poll failed (errno=%d)\n", errno);
usleep(100000);
}
} else {
// Some events occurred.
mPendingEventCount = size_t(pollResult);
}
}
// All done, return the number of events we read.
return events;
}
- 这里除了处理输入事件外,还会处理设备的添加、删除以及扫描。这里主要关注输入事件。
rust
// frameworks/native/services/inputflinger/reader/InputReader.cpp
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 {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED:
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
break;
default:
ALOG_ASSERT(false); // can't happen
break;
}
}
count -= batchSize;
rawEvent += batchSize;
}
return out;
}
- 找到事件所对应的InputDevice处理事件。
c
// frameworks/native/services/inputflinger/reader/InputReader.cpp
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 {};
}
return device->process(rawEvents, count);
}
- 把rawEvent交给设备相关的Mapper处理
rust
frameworks/native/services/inputflinger/reader/InputDevice.cpp
std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t count) {
std::list<NotifyArgs> out;
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) {
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
out += reset(rawEvent->when);
mDropUntilNextSync = false;
ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun.");
} else {
ALOGD_IF(debugRawEvents(),
"Dropped input event while waiting for next input sync.");
}
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
mDropUntilNextSync = true;
} else {
for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
out += mapper.process(*rawEvent);
});
}
--count;
}
postProcess(out);
return out;
}
- 设备相关的Mapper是在创建InputDevice的时候根据设备类型添加的。我们这里只分析触摸事件,可以看到和触摸事件相关的有
SingleTouchInputMapper
(单指触摸)和MultiTouchInputMapper
(多指触摸)。
less
std::vector<std::unique_ptr<InputMapper>> InputDevice::createMappers(
InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) {
ftl::Flags<InputDeviceClass> classes = contextPtr.getDeviceClasses();
std::vector<std::unique_ptr<InputMapper>> mappers;
// Switch-like devices.
if (classes.test(InputDeviceClass::SWITCH)) {
mappers.push_back(createInputMapper<SwitchInputMapper>(contextPtr, readerConfig));
}
// Scroll wheel-like devices.
if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
mappers.push_back(createInputMapper<RotaryEncoderInputMapper>(contextPtr, readerConfig));
}
// Vibrator-like devices.
if (classes.test(InputDeviceClass::VIBRATOR)) {
mappers.push_back(createInputMapper<VibratorInputMapper>(contextPtr, readerConfig));
}
// Battery-like devices or light-containing devices.
// PeripheralController will be created with associated EventHub device.
if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) {
mController = std::make_unique<PeripheralController>(contextPtr);
}
// Keyboard-like devices.
uint32_t keyboardSource = 0;
if (classes.test(InputDeviceClass::KEYBOARD)) {
keyboardSource |= AINPUT_SOURCE_KEYBOARD;
}
if (classes.test(InputDeviceClass::DPAD)) {
keyboardSource |= AINPUT_SOURCE_DPAD;
}
if (classes.test(InputDeviceClass::GAMEPAD)) {
keyboardSource |= AINPUT_SOURCE_GAMEPAD;
}
if (keyboardSource != 0) {
mappers.push_back(
createInputMapper<KeyboardInputMapper>(contextPtr, readerConfig, keyboardSource));
}
// Cursor-like devices.
if (classes.test(InputDeviceClass::CURSOR)) {
mappers.push_back(createInputMapper<CursorInputMapper>(contextPtr, readerConfig));
}
// Touchscreens and touchpad devices.
if (classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT)) {
mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
} else if (classes.test(InputDeviceClass::TOUCH_MT)) {
mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
} else if (classes.test(InputDeviceClass::TOUCH)) {
mappers.push_back(createInputMapper<SingleTouchInputMapper>(contextPtr, readerConfig));
}
// Joystick-like devices.
if (classes.test(InputDeviceClass::JOYSTICK)) {
mappers.push_back(createInputMapper<JoystickInputMapper>(contextPtr, readerConfig));
}
// Motion sensor enabled devices.
if (classes.test(InputDeviceClass::SENSOR)) {
mappers.push_back(createInputMapper<SensorInputMapper>(contextPtr, readerConfig));
}
// External stylus-like devices.
if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
mappers.push_back(createInputMapper<ExternalStylusInputMapper>(contextPtr, readerConfig));
}
return mappers;
}
- Mapper的主要作用是将设备上报的RawEvent累加到一起,为转换成InputEvent做准备。
c
// frameworks/native/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
std::list<NotifyArgs> SingleTouchInputMapper::process(const RawEvent& rawEvent) {
//TouchInputMapper是SingleTouchInputMapper的父类
std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
mSingleTouchMotionAccumulator.process(rawEvent);
return out;
}
- 我们可以用getevent工具查看下触摸屏上报的事件流,下面每一行都是一个RawEvent,第一列为type,第二列为code,第三列为value。
10. SingleTouchMotionAccumulator会根据RawEvent的code,将value存入对应的变量
ini
// frameworks/native/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
void SingleTouchMotionAccumulator::process(const RawEvent& rawEvent) {
if (rawEvent.type == EV_ABS) {
switch (rawEvent.code) {
case ABS_X:
mAbsX = rawEvent.value;
break;
case ABS_Y:
mAbsY = rawEvent.value;
break;
case ABS_PRESSURE:
mAbsPressure = rawEvent.value;
break;
case ABS_TOOL_WIDTH:
mAbsToolWidth = rawEvent.value;
break;
case ABS_DISTANCE:
mAbsDistance = rawEvent.value;
break;
case ABS_TILT_X:
mAbsTiltX = rawEvent.value;
break;
case ABS_TILT_Y:
mAbsTiltY = rawEvent.value;
break;
}
}
}
- 只有遇到type为
EV_SYN
code为SYN_REPORT
的RawEvent才会返回非空的NotifyArgs
列表。
scss
// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp
std::list<NotifyArgs> TouchInputMapper::process(const RawEvent& rawEvent) {
mCursorButtonAccumulator.process(rawEvent);
mCursorScrollAccumulator.process(rawEvent);
mTouchButtonAccumulator.process(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();
}
// Push a new state.
mRawStatesPending.emplace_back();
RawState& next = mRawStatesPending.back();
next.clear();
next.when = when;
next.readTime = readTime;
// Sync button state.
next.buttonState = filterButtonState(mConfig,
mTouchButtonAccumulator.getButtonState() |
mCursorButtonAccumulator.getButtonState());
// Sync scroll
next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
next.rawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
mCursorScrollAccumulator.finishSync();
// 同步触摸数据
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];
std::tie(next.when, next.readTime) =
applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), when,
readTime, last.when);
// Assign pointer ids.
if (!mHavePointerIds) {
assignPointerIds(last, next);
}
// 处理触摸
out += processRawTouches(/*timeout=*/false);
return out;
}
- 将
SingleTouchMotionAccumulator
数据同步到outState
中
ini
// frameworks/native/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
if (mTouchButtonAccumulator.isToolActive()) {
outState->rawPointerData.pointerCount = 1;
outState->rawPointerData.idToIndex[0] = 0;
bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE &&
(mTouchButtonAccumulator.isHovering() ||
(mRawPointerAxes.pressure.valid &&
mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0));
outState->rawPointerData.markIdBit(0, isHovering);
RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[0];
outPointer.id = 0;
outPointer.x = mSingleTouchMotionAccumulator.getAbsoluteX();
outPointer.y = mSingleTouchMotionAccumulator.getAbsoluteY();
outPointer.pressure = mSingleTouchMotionAccumulator.getAbsolutePressure();
outPointer.touchMajor = 0;
outPointer.touchMinor = 0;
outPointer.toolMajor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
outPointer.toolMinor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
outPointer.orientation = 0;
outPointer.distance = mSingleTouchMotionAccumulator.getAbsoluteDistance();
outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX();
outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY();
outPointer.toolType = mTouchButtonAccumulator.getToolType();
if (outPointer.toolType == ToolType::UNKNOWN) {
outPointer.toolType = ToolType::FINGER;
}
outPointer.isHovering = isHovering;
}
}