Create inputreader directory

In order to start the work to split InputReader.cpp into multiple files,
we first create a reader directory to hold the source files for
libinputreader.

The affected files are also reformatted with clang-format.

Bug: 140139676
Test: libinputflinger and libinputflinger_tests builds successfully for
  crosshatch

Change-Id: I62b6b873e2a03e61655eb6214183c984306dcfdb
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
new file mode 100644
index 0000000..c3e8ced
--- /dev/null
+++ b/services/inputflinger/reader/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_headers {
+    name: "libinputreader_headers",
+    export_include_dirs: ["include"],
+}
+
+cc_library_shared {
+    name: "libinputreader",
+    defaults: ["inputflinger_defaults"],
+
+    srcs: [
+        "EventHub.cpp",
+        "InputReader.cpp",
+        "InputReaderFactory.cpp",
+        "TouchVideoDevice.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libinputflinger_base",
+        "libcrypto",
+        "libcutils",
+        "libinput",
+        "liblog",
+        "libui",
+        "libutils",
+        "libhardware_legacy",
+    ],
+
+    header_libs: [
+        "libinputflinger_headers",
+        "libinputreader_headers",
+    ],
+
+    export_header_lib_headers: [
+        "libinputflinger_headers",
+    ],
+}
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
new file mode 100644
index 0000000..c15ecfd
--- /dev/null
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -0,0 +1,1945 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <memory.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+#include <sys/limits.h>
+#include <unistd.h>
+
+#define LOG_TAG "EventHub"
+
+// #define LOG_NDEBUG 0
+
+#include "EventHub.h"
+
+#include <hardware_legacy/power.h>
+
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <openssl/sha.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <utils/threads.h>
+
+#include <input/KeyCharacterMap.h>
+#include <input/KeyLayoutMap.h>
+#include <input/VirtualKeyMap.h>
+
+/* this macro is used to tell if "bit" is set in "array"
+ * it selects a byte from the array, and does a boolean AND
+ * operation with a byte that only has the relevant bit set.
+ * eg. to check for the 12th bit, we do (array[1] & 1<<4)
+ */
+#define test_bit(bit, array) ((array)[(bit) / 8] & (1 << ((bit) % 8)))
+
+/* this macro computes the number of bytes needed to represent a bit array of the specified size */
+#define sizeof_bit_array(bits) (((bits) + 7) / 8)
+
+#define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+
+using android::base::StringPrintf;
+
+namespace android {
+
+static constexpr bool DEBUG = false;
+
+static const char* WAKE_LOCK_ID = "KeyEvents";
+static const char* DEVICE_PATH = "/dev/input";
+// v4l2 devices go directly into /dev
+static const char* VIDEO_DEVICE_PATH = "/dev";
+
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+static std::string sha1(const std::string& in) {
+    SHA_CTX ctx;
+    SHA1_Init(&ctx);
+    SHA1_Update(&ctx, reinterpret_cast<const u_char*>(in.c_str()), in.size());
+    u_char digest[SHA_DIGEST_LENGTH];
+    SHA1_Final(digest, &ctx);
+
+    std::string out;
+    for (size_t i = 0; i < SHA_DIGEST_LENGTH; i++) {
+        out += StringPrintf("%02x", digest[i]);
+    }
+    return out;
+}
+
+/**
+ * Return true if name matches "v4l-touch*"
+ */
+static bool isV4lTouchNode(const char* name) {
+    return strstr(name, "v4l-touch") == name;
+}
+
+/**
+ * Returns true if V4L devices should be scanned.
+ *
+ * The system property ro.input.video_enabled can be used to control whether
+ * EventHub scans and opens V4L devices. As V4L does not support multiple
+ * clients, EventHub effectively blocks access to these devices when it opens
+ * them.
+ *
+ * Setting this to "false" would prevent any video devices from being discovered and
+ * associated with input devices.
+ *
+ * This property can be used as follows:
+ * 1. To turn off features that are dependent on video device presence.
+ * 2. During testing and development, to allow other clients to read video devices
+ * directly from /dev.
+ */
+static bool isV4lScanningEnabled() {
+    return property_get_bool("ro.input.video_enabled", true /* default_value */);
+}
+
+static nsecs_t processEventTimestamp(const struct input_event& event) {
+    // Use the time specified in the event instead of the current time
+    // so that downstream code can get more accurate estimates of
+    // event dispatch latency from the time the event is enqueued onto
+    // the evdev client buffer.
+    //
+    // The event's timestamp fortuitously uses the same monotonic clock
+    // time base as the rest of Android. The kernel event device driver
+    // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
+    // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
+    // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
+    // system call that also queries ktime_get_ts().
+
+    const nsecs_t inputEventTime = seconds_to_nanoseconds(event.time.tv_sec) +
+            microseconds_to_nanoseconds(event.time.tv_usec);
+    return inputEventTime;
+}
+
+// --- Global Functions ---
+
+uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) {
+    // Touch devices get dibs on touch-related axes.
+    if (deviceClasses & INPUT_DEVICE_CLASS_TOUCH) {
+        switch (axis) {
+            case ABS_X:
+            case ABS_Y:
+            case ABS_PRESSURE:
+            case ABS_TOOL_WIDTH:
+            case ABS_DISTANCE:
+            case ABS_TILT_X:
+            case ABS_TILT_Y:
+            case ABS_MT_SLOT:
+            case ABS_MT_TOUCH_MAJOR:
+            case ABS_MT_TOUCH_MINOR:
+            case ABS_MT_WIDTH_MAJOR:
+            case ABS_MT_WIDTH_MINOR:
+            case ABS_MT_ORIENTATION:
+            case ABS_MT_POSITION_X:
+            case ABS_MT_POSITION_Y:
+            case ABS_MT_TOOL_TYPE:
+            case ABS_MT_BLOB_ID:
+            case ABS_MT_TRACKING_ID:
+            case ABS_MT_PRESSURE:
+            case ABS_MT_DISTANCE:
+                return INPUT_DEVICE_CLASS_TOUCH;
+        }
+    }
+
+    // External stylus gets the pressure axis
+    if (deviceClasses & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS) {
+        if (axis == ABS_PRESSURE) {
+            return INPUT_DEVICE_CLASS_EXTERNAL_STYLUS;
+        }
+    }
+
+    // Joystick devices get the rest.
+    return deviceClasses & INPUT_DEVICE_CLASS_JOYSTICK;
+}
+
+// --- EventHub::Device ---
+
+EventHub::Device::Device(int fd, int32_t id, const std::string& path,
+                         const InputDeviceIdentifier& identifier)
+      : next(nullptr),
+        fd(fd),
+        id(id),
+        path(path),
+        identifier(identifier),
+        classes(0),
+        configuration(nullptr),
+        virtualKeyMap(nullptr),
+        ffEffectPlaying(false),
+        ffEffectId(-1),
+        controllerNumber(0),
+        enabled(true),
+        isVirtual(fd < 0) {
+    memset(keyBitmask, 0, sizeof(keyBitmask));
+    memset(absBitmask, 0, sizeof(absBitmask));
+    memset(relBitmask, 0, sizeof(relBitmask));
+    memset(swBitmask, 0, sizeof(swBitmask));
+    memset(ledBitmask, 0, sizeof(ledBitmask));
+    memset(ffBitmask, 0, sizeof(ffBitmask));
+    memset(propBitmask, 0, sizeof(propBitmask));
+}
+
+EventHub::Device::~Device() {
+    close();
+    delete configuration;
+}
+
+void EventHub::Device::close() {
+    if (fd >= 0) {
+        ::close(fd);
+        fd = -1;
+    }
+}
+
+status_t EventHub::Device::enable() {
+    fd = open(path.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
+    if (fd < 0) {
+        ALOGE("could not open %s, %s\n", path.c_str(), strerror(errno));
+        return -errno;
+    }
+    enabled = true;
+    return OK;
+}
+
+status_t EventHub::Device::disable() {
+    close();
+    enabled = false;
+    return OK;
+}
+
+bool EventHub::Device::hasValidFd() {
+    return !isVirtual && enabled;
+}
+
+// --- EventHub ---
+
+const int EventHub::EPOLL_MAX_EVENTS;
+
+EventHub::EventHub(void)
+      : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
+        mNextDeviceId(1),
+        mControllerNumbers(),
+        mOpeningDevices(nullptr),
+        mClosingDevices(nullptr),
+        mNeedToSendFinishedDeviceScan(false),
+        mNeedToReopenDevices(false),
+        mNeedToScanDevices(true),
+        mPendingEventCount(0),
+        mPendingEventIndex(0),
+        mPendingINotify(false) {
+    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+
+    mEpollFd = epoll_create1(EPOLL_CLOEXEC);
+    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
+
+    mINotifyFd = inotify_init();
+    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
+    LOG_ALWAYS_FATAL_IF(mInputWd < 0, "Could not register INotify for %s: %s", DEVICE_PATH,
+                        strerror(errno));
+    if (isV4lScanningEnabled()) {
+        mVideoWd = inotify_add_watch(mINotifyFd, VIDEO_DEVICE_PATH, IN_DELETE | IN_CREATE);
+        LOG_ALWAYS_FATAL_IF(mVideoWd < 0, "Could not register INotify for %s: %s",
+                            VIDEO_DEVICE_PATH, strerror(errno));
+    } else {
+        mVideoWd = -1;
+        ALOGI("Video device scanning disabled");
+    }
+
+    struct epoll_event eventItem;
+    memset(&eventItem, 0, sizeof(eventItem));
+    eventItem.events = EPOLLIN;
+    eventItem.data.fd = mINotifyFd;
+    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);
+
+    int wakeFds[2];
+    result = pipe(wakeFds);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
+
+    mWakeReadPipeFd = wakeFds[0];
+    mWakeWritePipeFd = wakeFds[1];
+
+    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
+                        errno);
+
+    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
+                        errno);
+
+    eventItem.data.fd = mWakeReadPipeFd;
+    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
+                        errno);
+}
+
+EventHub::~EventHub(void) {
+    closeAllDevicesLocked();
+
+    while (mClosingDevices) {
+        Device* device = mClosingDevices;
+        mClosingDevices = device->next;
+        delete device;
+    }
+
+    ::close(mEpollFd);
+    ::close(mINotifyFd);
+    ::close(mWakeReadPipeFd);
+    ::close(mWakeWritePipeFd);
+
+    release_wake_lock(WAKE_LOCK_ID);
+}
+
+InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) return InputDeviceIdentifier();
+    return device->identifier;
+}
+
+uint32_t EventHub::getDeviceClasses(int32_t deviceId) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) return 0;
+    return device->classes;
+}
+
+int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) return 0;
+    return device->controllerNumber;
+}
+
+void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device && device->configuration) {
+        *outConfiguration = *device->configuration;
+    } else {
+        outConfiguration->clear();
+    }
+}
+
+status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
+                                       RawAbsoluteAxisInfo* outAxisInfo) const {
+    outAxisInfo->clear();
+
+    if (axis >= 0 && axis <= ABS_MAX) {
+        AutoMutex _l(mLock);
+
+        Device* device = getDeviceLocked(deviceId);
+        if (device && device->hasValidFd() && test_bit(axis, device->absBitmask)) {
+            struct input_absinfo info;
+            if (ioctl(device->fd, EVIOCGABS(axis), &info)) {
+                ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
+                      device->identifier.name.c_str(), device->fd, errno);
+                return -errno;
+            }
+
+            if (info.minimum != info.maximum) {
+                outAxisInfo->valid = true;
+                outAxisInfo->minValue = info.minimum;
+                outAxisInfo->maxValue = info.maximum;
+                outAxisInfo->flat = info.flat;
+                outAxisInfo->fuzz = info.fuzz;
+                outAxisInfo->resolution = info.resolution;
+            }
+            return OK;
+        }
+    }
+    return -1;
+}
+
+bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
+    if (axis >= 0 && axis <= REL_MAX) {
+        AutoMutex _l(mLock);
+
+        Device* device = getDeviceLocked(deviceId);
+        if (device) {
+            return test_bit(axis, device->relBitmask);
+        }
+    }
+    return false;
+}
+
+bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
+    if (property >= 0 && property <= INPUT_PROP_MAX) {
+        AutoMutex _l(mLock);
+
+        Device* device = getDeviceLocked(deviceId);
+        if (device) {
+            return test_bit(property, device->propBitmask);
+        }
+    }
+    return false;
+}
+
+int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+    if (scanCode >= 0 && scanCode <= KEY_MAX) {
+        AutoMutex _l(mLock);
+
+        Device* device = getDeviceLocked(deviceId);
+        if (device && device->hasValidFd() && test_bit(scanCode, device->keyBitmask)) {
+            uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+            memset(keyState, 0, sizeof(keyState));
+            if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+                return test_bit(scanCode, keyState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+            }
+        }
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+    AutoMutex _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device && device->hasValidFd() && device->keyMap.haveKeyLayout()) {
+        std::vector<int32_t> scanCodes;
+        device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode, &scanCodes);
+        if (scanCodes.size() != 0) {
+            uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+            memset(keyState, 0, sizeof(keyState));
+            if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+                for (size_t i = 0; i < scanCodes.size(); i++) {
+                    int32_t sc = scanCodes[i];
+                    if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, keyState)) {
+                        return AKEY_STATE_DOWN;
+                    }
+                }
+                return AKEY_STATE_UP;
+            }
+        }
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
+    if (sw >= 0 && sw <= SW_MAX) {
+        AutoMutex _l(mLock);
+
+        Device* device = getDeviceLocked(deviceId);
+        if (device && device->hasValidFd() && test_bit(sw, device->swBitmask)) {
+            uint8_t swState[sizeof_bit_array(SW_MAX + 1)];
+            memset(swState, 0, sizeof(swState));
+            if (ioctl(device->fd, EVIOCGSW(sizeof(swState)), swState) >= 0) {
+                return test_bit(sw, swState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+            }
+        }
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
+    *outValue = 0;
+
+    if (axis >= 0 && axis <= ABS_MAX) {
+        AutoMutex _l(mLock);
+
+        Device* device = getDeviceLocked(deviceId);
+        if (device && device->hasValidFd() && test_bit(axis, device->absBitmask)) {
+            struct input_absinfo info;
+            if (ioctl(device->fd, EVIOCGABS(axis), &info)) {
+                ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
+                      device->identifier.name.c_str(), device->fd, errno);
+                return -errno;
+            }
+
+            *outValue = info.value;
+            return OK;
+        }
+    }
+    return -1;
+}
+
+bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+                                     uint8_t* outFlags) const {
+    AutoMutex _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device && device->keyMap.haveKeyLayout()) {
+        std::vector<int32_t> scanCodes;
+        for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
+            scanCodes.clear();
+
+            status_t err = device->keyMap.keyLayoutMap->findScanCodesForKey(keyCodes[codeIndex],
+                                                                            &scanCodes);
+            if (!err) {
+                // check the possible scan codes identified by the layout map against the
+                // map of codes actually emitted by the driver
+                for (size_t sc = 0; sc < scanCodes.size(); sc++) {
+                    if (test_bit(scanCodes[sc], device->keyBitmask)) {
+                        outFlags[codeIndex] = 1;
+                        break;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
+                          int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    status_t status = NAME_NOT_FOUND;
+
+    if (device) {
+        // Check the key character map first.
+        sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
+        if (kcm != nullptr) {
+            if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
+                *outFlags = 0;
+                status = NO_ERROR;
+            }
+        }
+
+        // Check the key layout next.
+        if (status != NO_ERROR && device->keyMap.haveKeyLayout()) {
+            if (!device->keyMap.keyLayoutMap->mapKey(scanCode, usageCode, outKeycode, outFlags)) {
+                status = NO_ERROR;
+            }
+        }
+
+        if (status == NO_ERROR) {
+            if (kcm != nullptr) {
+                kcm->tryRemapKey(*outKeycode, metaState, outKeycode, outMetaState);
+            } else {
+                *outMetaState = metaState;
+            }
+        }
+    }
+
+    if (status != NO_ERROR) {
+        *outKeycode = 0;
+        *outFlags = 0;
+        *outMetaState = metaState;
+    }
+
+    return status;
+}
+
+status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+
+    if (device && device->keyMap.haveKeyLayout()) {
+        status_t err = device->keyMap.keyLayoutMap->mapAxis(scanCode, outAxisInfo);
+        if (err == NO_ERROR) {
+            return NO_ERROR;
+        }
+    }
+
+    return NAME_NOT_FOUND;
+}
+
+void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
+    AutoMutex _l(mLock);
+
+    mExcludedDevices = devices;
+}
+
+bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device && scanCode >= 0 && scanCode <= KEY_MAX) {
+        if (test_bit(scanCode, device->keyBitmask)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    int32_t sc;
+    if (device && mapLed(device, led, &sc) == NO_ERROR) {
+        if (test_bit(sc, device->ledBitmask)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    setLedStateLocked(device, led, on);
+}
+
+void EventHub::setLedStateLocked(Device* device, int32_t led, bool on) {
+    int32_t sc;
+    if (device && device->hasValidFd() && mapLed(device, led, &sc) != NAME_NOT_FOUND) {
+        struct input_event ev;
+        ev.time.tv_sec = 0;
+        ev.time.tv_usec = 0;
+        ev.type = EV_LED;
+        ev.code = sc;
+        ev.value = on ? 1 : 0;
+
+        ssize_t nWrite;
+        do {
+            nWrite = write(device->fd, &ev, sizeof(struct input_event));
+        } while (nWrite == -1 && errno == EINTR);
+    }
+}
+
+void EventHub::getVirtualKeyDefinitions(int32_t deviceId,
+                                        std::vector<VirtualKeyDefinition>& outVirtualKeys) const {
+    outVirtualKeys.clear();
+
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device && device->virtualKeyMap) {
+        const std::vector<VirtualKeyDefinition> virtualKeys =
+                device->virtualKeyMap->getVirtualKeys();
+        outVirtualKeys.insert(outVirtualKeys.end(), virtualKeys.begin(), virtualKeys.end());
+    }
+}
+
+sp<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device) {
+        return device->getKeyCharacterMap();
+    }
+    return nullptr;
+}
+
+bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device) {
+        if (map != device->overlayKeyMap) {
+            device->overlayKeyMap = map;
+            device->combinedKeyMap = KeyCharacterMap::combine(device->keyMap.keyCharacterMap, map);
+            return true;
+        }
+    }
+    return false;
+}
+
+static std::string generateDescriptor(InputDeviceIdentifier& identifier) {
+    std::string rawDescriptor;
+    rawDescriptor += StringPrintf(":%04x:%04x:", identifier.vendor, identifier.product);
+    // TODO add handling for USB devices to not uniqueify kbs that show up twice
+    if (!identifier.uniqueId.empty()) {
+        rawDescriptor += "uniqueId:";
+        rawDescriptor += identifier.uniqueId;
+    } else if (identifier.nonce != 0) {
+        rawDescriptor += StringPrintf("nonce:%04x", identifier.nonce);
+    }
+
+    if (identifier.vendor == 0 && identifier.product == 0) {
+        // If we don't know the vendor and product id, then the device is probably
+        // built-in so we need to rely on other information to uniquely identify
+        // the input device.  Usually we try to avoid relying on the device name or
+        // location but for built-in input device, they are unlikely to ever change.
+        if (!identifier.name.empty()) {
+            rawDescriptor += "name:";
+            rawDescriptor += identifier.name;
+        } else if (!identifier.location.empty()) {
+            rawDescriptor += "location:";
+            rawDescriptor += identifier.location;
+        }
+    }
+    identifier.descriptor = sha1(rawDescriptor);
+    return rawDescriptor;
+}
+
+void EventHub::assignDescriptorLocked(InputDeviceIdentifier& identifier) {
+    // Compute a device descriptor that uniquely identifies the device.
+    // The descriptor is assumed to be a stable identifier.  Its value should not
+    // change between reboots, reconnections, firmware updates or new releases
+    // of Android. In practice we sometimes get devices that cannot be uniquely
+    // identified. In this case we enforce uniqueness between connected devices.
+    // Ideally, we also want the descriptor to be short and relatively opaque.
+
+    identifier.nonce = 0;
+    std::string rawDescriptor = generateDescriptor(identifier);
+    if (identifier.uniqueId.empty()) {
+        // If it didn't have a unique id check for conflicts and enforce
+        // uniqueness if necessary.
+        while (getDeviceByDescriptorLocked(identifier.descriptor) != nullptr) {
+            identifier.nonce++;
+            rawDescriptor = generateDescriptor(identifier);
+        }
+    }
+    ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.c_str(),
+          identifier.descriptor.c_str());
+}
+
+void EventHub::vibrate(int32_t deviceId, nsecs_t duration) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device && device->hasValidFd()) {
+        ff_effect effect;
+        memset(&effect, 0, sizeof(effect));
+        effect.type = FF_RUMBLE;
+        effect.id = device->ffEffectId;
+        effect.u.rumble.strong_magnitude = 0xc000;
+        effect.u.rumble.weak_magnitude = 0xc000;
+        effect.replay.length = (duration + 999999LL) / 1000000LL;
+        effect.replay.delay = 0;
+        if (ioctl(device->fd, EVIOCSFF, &effect)) {
+            ALOGW("Could not upload force feedback effect to device %s due to error %d.",
+                  device->identifier.name.c_str(), errno);
+            return;
+        }
+        device->ffEffectId = effect.id;
+
+        struct input_event ev;
+        ev.time.tv_sec = 0;
+        ev.time.tv_usec = 0;
+        ev.type = EV_FF;
+        ev.code = device->ffEffectId;
+        ev.value = 1;
+        if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+            ALOGW("Could not start force feedback effect on device %s due to error %d.",
+                  device->identifier.name.c_str(), errno);
+            return;
+        }
+        device->ffEffectPlaying = true;
+    }
+}
+
+void EventHub::cancelVibrate(int32_t deviceId) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device && device->hasValidFd()) {
+        if (device->ffEffectPlaying) {
+            device->ffEffectPlaying = false;
+
+            struct input_event ev;
+            ev.time.tv_sec = 0;
+            ev.time.tv_usec = 0;
+            ev.type = EV_FF;
+            ev.code = device->ffEffectId;
+            ev.value = 0;
+            if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+                ALOGW("Could not stop force feedback effect on device %s due to error %d.",
+                      device->identifier.name.c_str(), errno);
+                return;
+            }
+        }
+    }
+}
+
+EventHub::Device* EventHub::getDeviceByDescriptorLocked(const std::string& descriptor) const {
+    size_t size = mDevices.size();
+    for (size_t i = 0; i < size; i++) {
+        Device* device = mDevices.valueAt(i);
+        if (descriptor == device->identifier.descriptor) {
+            return device;
+        }
+    }
+    return nullptr;
+}
+
+EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
+    if (deviceId == ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID) {
+        deviceId = mBuiltInKeyboardId;
+    }
+    ssize_t index = mDevices.indexOfKey(deviceId);
+    return index >= 0 ? mDevices.valueAt(index) : NULL;
+}
+
+EventHub::Device* EventHub::getDeviceByPathLocked(const char* devicePath) const {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        Device* device = mDevices.valueAt(i);
+        if (device->path == devicePath) {
+            return device;
+        }
+    }
+    return nullptr;
+}
+
+/**
+ * The file descriptor could be either input device, or a video device (associated with a
+ * specific input device). Check both cases here, and return the device that this event
+ * belongs to. Caller can compare the fd's once more to determine event type.
+ * Looks through all input devices, and only attached video devices. Unattached video
+ * devices are ignored.
+ */
+EventHub::Device* EventHub::getDeviceByFdLocked(int fd) const {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        Device* device = mDevices.valueAt(i);
+        if (device->fd == fd) {
+            // This is an input device event
+            return device;
+        }
+        if (device->videoDevice && device->videoDevice->getFd() == fd) {
+            // This is a video device event
+            return device;
+        }
+    }
+    // We do not check mUnattachedVideoDevices here because they should not participate in epoll,
+    // and therefore should never be looked up by fd.
+    return nullptr;
+}
+
+size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
+    ALOG_ASSERT(bufferSize >= 1);
+
+    AutoMutex _l(mLock);
+
+    struct input_event readBuffer[bufferSize];
+
+    RawEvent* event = buffer;
+    size_t capacity = bufferSize;
+    bool awoken = false;
+    for (;;) {
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+        // Reopen input devices if needed.
+        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
+        }
+
+        // Report any devices that had last been added/removed.
+        while (mClosingDevices) {
+            Device* device = mClosingDevices;
+            ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str());
+            mClosingDevices = device->next;
+            event->when = now;
+            event->deviceId = (device->id == mBuiltInKeyboardId)
+                    ? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID
+                    : device->id;
+            event->type = DEVICE_REMOVED;
+            event += 1;
+            delete device;
+            mNeedToSendFinishedDeviceScan = true;
+            if (--capacity == 0) {
+                break;
+            }
+        }
+
+        if (mNeedToScanDevices) {
+            mNeedToScanDevices = false;
+            scanDevicesLocked();
+            mNeedToSendFinishedDeviceScan = true;
+        }
+
+        while (mOpeningDevices != nullptr) {
+            Device* device = mOpeningDevices;
+            ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());
+            mOpeningDevices = device->next;
+            event->when = now;
+            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+            event->type = DEVICE_ADDED;
+            event += 1;
+            mNeedToSendFinishedDeviceScan = true;
+            if (--capacity == 0) {
+                break;
+            }
+        }
+
+        if (mNeedToSendFinishedDeviceScan) {
+            mNeedToSendFinishedDeviceScan = false;
+            event->when = now;
+            event->type = FINISHED_DEVICE_SCAN;
+            event += 1;
+            if (--capacity == 0) {
+                break;
+            }
+        }
+
+        // Grab the next input event.
+        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 buffer[16];
+                    ssize_t nRead;
+                    do {
+                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+                } else {
+                    ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
+                          eventItem.events);
+                }
+                continue;
+            }
+
+            Device* device = getDeviceByFdLocked(eventItem.data.fd);
+            if (!device) {
+                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, sizeof(struct input_event) * capacity);
+                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
+                    // Device was removed before INotify noticed.
+                    ALOGW("could not get event, removed? (fd: %d size: %" PRId32
+                          " bufferSize: %zu capacity: %zu errno: %d)\n",
+                          device->fd, readSize, bufferSize, capacity, 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 {
+                    int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+
+                    size_t count = size_t(readSize) / sizeof(struct input_event);
+                    for (size_t i = 0; i < count; i++) {
+                        struct input_event& iev = readBuffer[i];
+                        event->when = processEventTimestamp(iev);
+                        event->deviceId = deviceId;
+                        event->type = iev.type;
+                        event->code = iev.code;
+                        event->value = iev.value;
+                        event += 1;
+                        capacity -= 1;
+                    }
+                    if (capacity == 0) {
+                        // 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;
+            readNotifyLocked();
+            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 (event != buffer || awoken) {
+            break;
+        }
+
+        // Poll for events.  Mind the wake lock dance!
+        // We hold a wake lock at all times except during epoll_wait().  This works due to some
+        // subtle choreography.  When a device driver has pending (unread) events, it acquires
+        // a kernel wake lock.  However, once the last pending event has been read, the device
+        // driver will release the kernel wake lock.  To prevent the system from going to sleep
+        // when this happens, the EventHub holds onto its own user wake lock while the client
+        // is processing events.  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, must be before release_wake_lock
+        release_wake_lock(WAKE_LOCK_ID);
+
+        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+
+        acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+        mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock
+
+        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 event - buffer;
+}
+
+std::vector<TouchVideoFrame> EventHub::getVideoFrames(int32_t deviceId) {
+    AutoMutex _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (!device || !device->videoDevice) {
+        return {};
+    }
+    return device->videoDevice->consumeFrames();
+}
+
+void EventHub::wake() {
+    ALOGV("wake() called");
+
+    ssize_t nWrite;
+    do {
+        nWrite = write(mWakeWritePipeFd, "W", 1);
+    } while (nWrite == -1 && errno == EINTR);
+
+    if (nWrite != 1 && errno != EAGAIN) {
+        ALOGW("Could not write wake signal: %s", strerror(errno));
+    }
+}
+
+void EventHub::scanDevicesLocked() {
+    status_t result = scanDirLocked(DEVICE_PATH);
+    if (result < 0) {
+        ALOGE("scan dir failed for %s", DEVICE_PATH);
+    }
+    if (isV4lScanningEnabled()) {
+        result = scanVideoDirLocked(VIDEO_DEVICE_PATH);
+        if (result != OK) {
+            ALOGE("scan video dir failed for %s", VIDEO_DEVICE_PATH);
+        }
+    }
+    if (mDevices.indexOfKey(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) < 0) {
+        createVirtualKeyboardLocked();
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) {
+    const uint8_t* end = array + endIndex;
+    array += startIndex;
+    while (array != end) {
+        if (*(array++) != 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static const int32_t GAMEPAD_KEYCODES[] = {
+        AKEYCODE_BUTTON_A,      AKEYCODE_BUTTON_B,      AKEYCODE_BUTTON_C,    //
+        AKEYCODE_BUTTON_X,      AKEYCODE_BUTTON_Y,      AKEYCODE_BUTTON_Z,    //
+        AKEYCODE_BUTTON_L1,     AKEYCODE_BUTTON_R1,                           //
+        AKEYCODE_BUTTON_L2,     AKEYCODE_BUTTON_R2,                           //
+        AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR,                       //
+        AKEYCODE_BUTTON_START,  AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE, //
+};
+
+status_t EventHub::registerFdForEpoll(int fd) {
+    // TODO(b/121395353) - consider adding EPOLLRDHUP
+    struct epoll_event eventItem = {};
+    eventItem.events = EPOLLIN | EPOLLWAKEUP;
+    eventItem.data.fd = fd;
+    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
+        ALOGE("Could not add fd to epoll instance: %s", strerror(errno));
+        return -errno;
+    }
+    return OK;
+}
+
+status_t EventHub::unregisterFdFromEpoll(int fd) {
+    if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, nullptr)) {
+        ALOGW("Could not remove fd from epoll instance: %s", strerror(errno));
+        return -errno;
+    }
+    return OK;
+}
+
+status_t EventHub::registerDeviceForEpollLocked(Device* device) {
+    if (device == nullptr) {
+        if (DEBUG) {
+            LOG_ALWAYS_FATAL("Cannot call registerDeviceForEpollLocked with null Device");
+        }
+        return BAD_VALUE;
+    }
+    status_t result = registerFdForEpoll(device->fd);
+    if (result != OK) {
+        ALOGE("Could not add input device fd to epoll for device %" PRId32, device->id);
+        return result;
+    }
+    if (device->videoDevice) {
+        registerVideoDeviceForEpollLocked(*device->videoDevice);
+    }
+    return result;
+}
+
+void EventHub::registerVideoDeviceForEpollLocked(const TouchVideoDevice& videoDevice) {
+    status_t result = registerFdForEpoll(videoDevice.getFd());
+    if (result != OK) {
+        ALOGE("Could not add video device %s to epoll", videoDevice.getName().c_str());
+    }
+}
+
+status_t EventHub::unregisterDeviceFromEpollLocked(Device* device) {
+    if (device->hasValidFd()) {
+        status_t result = unregisterFdFromEpoll(device->fd);
+        if (result != OK) {
+            ALOGW("Could not remove input device fd from epoll for device %" PRId32, device->id);
+            return result;
+        }
+    }
+    if (device->videoDevice) {
+        unregisterVideoDeviceFromEpollLocked(*device->videoDevice);
+    }
+    return OK;
+}
+
+void EventHub::unregisterVideoDeviceFromEpollLocked(const TouchVideoDevice& videoDevice) {
+    if (videoDevice.hasValidFd()) {
+        status_t result = unregisterFdFromEpoll(videoDevice.getFd());
+        if (result != OK) {
+            ALOGW("Could not remove video device fd from epoll for device: %s",
+                  videoDevice.getName().c_str());
+        }
+    }
+}
+
+status_t EventHub::openDeviceLocked(const char* devicePath) {
+    char buffer[80];
+
+    ALOGV("Opening device: %s", devicePath);
+
+    int fd = open(devicePath, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+    if (fd < 0) {
+        ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
+        return -1;
+    }
+
+    InputDeviceIdentifier identifier;
+
+    // Get device name.
+    if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
+        ALOGE("Could not get device name for %s: %s", devicePath, strerror(errno));
+    } else {
+        buffer[sizeof(buffer) - 1] = '\0';
+        identifier.name = buffer;
+    }
+
+    // Check to see if the device is on our excluded list
+    for (size_t i = 0; i < mExcludedDevices.size(); i++) {
+        const std::string& item = mExcludedDevices[i];
+        if (identifier.name == item) {
+            ALOGI("ignoring event id %s driver %s\n", devicePath, item.c_str());
+            close(fd);
+            return -1;
+        }
+    }
+
+    // Get device driver version.
+    int driverVersion;
+    if (ioctl(fd, EVIOCGVERSION, &driverVersion)) {
+        ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));
+        close(fd);
+        return -1;
+    }
+
+    // Get device identifier.
+    struct input_id inputId;
+    if (ioctl(fd, EVIOCGID, &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;
+
+    // Get device physical location.
+    if (ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {
+        // fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
+    } else {
+        buffer[sizeof(buffer) - 1] = '\0';
+        identifier.location = buffer;
+    }
+
+    // Get device unique 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 = buffer;
+    }
+
+    // Fill in the descriptor.
+    assignDescriptorLocked(identifier);
+
+    // Allocate device.  (The device object takes ownership of the fd at this point.)
+    int32_t deviceId = mNextDeviceId++;
+    Device* device = new Device(fd, deviceId, devicePath, identifier);
+
+    ALOGV("add device %d: %s\n", deviceId, devicePath);
+    ALOGV("  bus:        %04x\n"
+          "  vendor      %04x\n"
+          "  product     %04x\n"
+          "  version     %04x\n",
+          identifier.bus, identifier.vendor, identifier.product, identifier.version);
+    ALOGV("  name:       \"%s\"\n", identifier.name.c_str());
+    ALOGV("  location:   \"%s\"\n", identifier.location.c_str());
+    ALOGV("  unique id:  \"%s\"\n", identifier.uniqueId.c_str());
+    ALOGV("  descriptor: \"%s\"\n", identifier.descriptor.c_str());
+    ALOGV("  driver:     v%d.%d.%d\n", driverVersion >> 16, (driverVersion >> 8) & 0xff,
+          driverVersion & 0xff);
+
+    // Load the configuration file for the device.
+    loadConfigurationLocked(device);
+
+    // Figure out the kinds of events the device reports.
+    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);
+    ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
+    ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
+    ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
+    ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
+
+    // See if this is a keyboard.  Ignore everything in the button range except for
+    // joystick and gamepad buttons which are handled like keyboards for the most part.
+    bool haveKeyboardKeys =
+            containsNonZeroByte(device->keyBitmask, 0, sizeof_bit_array(BTN_MISC)) ||
+            containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),
+                                sizeof_bit_array(KEY_MAX + 1));
+    bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_MISC),
+                                                  sizeof_bit_array(BTN_MOUSE)) ||
+            containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_JOYSTICK),
+                                sizeof_bit_array(BTN_DIGI));
+    if (haveKeyboardKeys || haveGamepadButtons) {
+        device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+    }
+
+    // See if this is a cursor device such as a trackball or mouse.
+    if (test_bit(BTN_MOUSE, device->keyBitmask) && test_bit(REL_X, device->relBitmask) &&
+        test_bit(REL_Y, device->relBitmask)) {
+        device->classes |= INPUT_DEVICE_CLASS_CURSOR;
+    }
+
+    // See if this is a rotary encoder type device.
+    String8 deviceType = String8();
+    if (device->configuration &&
+        device->configuration->tryGetProperty(String8("device.type"), deviceType)) {
+        if (!deviceType.compare(String8("rotaryEncoder"))) {
+            device->classes |= INPUT_DEVICE_CLASS_ROTARY_ENCODER;
+        }
+    }
+
+    // See if this is a touch pad.
+    // Is this a new modern multi-touch driver?
+    if (test_bit(ABS_MT_POSITION_X, device->absBitmask) &&
+        test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
+        // Some joysticks such as the PS3 controller report axes that conflict
+        // with the ABS_MT range.  Try to confirm that the device really is
+        // a touch screen.
+        if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {
+            device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
+        }
+        // Is this an old style single-touch driver?
+    } 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;
+        // Is this a BT stylus?
+    } else if ((test_bit(ABS_PRESSURE, device->absBitmask) ||
+                test_bit(BTN_TOUCH, device->keyBitmask)) &&
+               !test_bit(ABS_X, device->absBitmask) && !test_bit(ABS_Y, device->absBitmask)) {
+        device->classes |= INPUT_DEVICE_CLASS_EXTERNAL_STYLUS;
+        // Keyboard will try to claim some of the buttons but we really want to reserve those so we
+        // can fuse it with the touch screen data, so just take them back. Note this means an
+        // external stylus cannot also be a keyboard device.
+        device->classes &= ~INPUT_DEVICE_CLASS_KEYBOARD;
+    }
+
+    // See if this device is a joystick.
+    // Assumes that joysticks always have gamepad buttons in order to distinguish them
+    // from other devices such as accelerometers that also have absolute axes.
+    if (haveGamepadButtons) {
+        uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK;
+        for (int i = 0; i <= ABS_MAX; i++) {
+            if (test_bit(i, device->absBitmask) &&
+                (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) {
+                device->classes = assumedClasses;
+                break;
+            }
+        }
+    }
+
+    // Check whether this device has switches.
+    for (int i = 0; i <= SW_MAX; i++) {
+        if (test_bit(i, device->swBitmask)) {
+            device->classes |= INPUT_DEVICE_CLASS_SWITCH;
+            break;
+        }
+    }
+
+    // Check whether this device supports the vibrator.
+    if (test_bit(FF_RUMBLE, device->ffBitmask)) {
+        device->classes |= INPUT_DEVICE_CLASS_VIBRATOR;
+    }
+
+    // Configure virtual keys.
+    if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
+        // Load the virtual keys for the touch screen, if any.
+        // We do this now so that we can make sure to load the keymap if necessary.
+        bool success = loadVirtualKeyMapLocked(device);
+        if (success) {
+            device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+        }
+    }
+
+    // Load the key map.
+    // We need to do this for joysticks too because the key layout may specify axes.
+    status_t keyMapStatus = NAME_NOT_FOUND;
+    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
+        // Load the keymap for the device.
+        keyMapStatus = loadKeyMapLocked(device);
+    }
+
+    // Configure the keyboard, gamepad or virtual keyboard.
+    if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+        // Register the keyboard as a built-in keyboard if it is eligible.
+        if (!keyMapStatus && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD &&
+            isEligibleBuiltInKeyboard(device->identifier, device->configuration, &device->keyMap)) {
+            mBuiltInKeyboardId = device->id;
+        }
+
+        // 'Q' key support = cheap test of whether this is an alpha-capable kbd
+        if (hasKeycodeLocked(device, AKEYCODE_Q)) {
+            device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
+        }
+
+        // See if this device has a DPAD.
+        if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
+            hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
+            hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
+            hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
+            hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
+            device->classes |= INPUT_DEVICE_CLASS_DPAD;
+        }
+
+        // See if this device has a gamepad.
+        for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES) / sizeof(GAMEPAD_KEYCODES[0]); i++) {
+            if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
+                device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
+                break;
+            }
+        }
+    }
+
+    // If the device isn't recognized as something we handle, don't monitor it.
+    if (device->classes == 0) {
+        ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath,
+              device->identifier.name.c_str());
+        delete device;
+        return -1;
+    }
+
+    // Determine whether the device has a mic.
+    if (deviceHasMicLocked(device)) {
+        device->classes |= INPUT_DEVICE_CLASS_MIC;
+    }
+
+    // Determine whether the device is external or internal.
+    if (isExternalDeviceLocked(device)) {
+        device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
+    }
+
+    if (device->classes & (INPUT_DEVICE_CLASS_JOYSTICK | INPUT_DEVICE_CLASS_DPAD) &&
+        device->classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+        device->controllerNumber = getNextControllerNumberLocked(device);
+        setLedForControllerLocked(device);
+    }
+
+    // Find a matching video device by comparing device names
+    // This should be done before registerDeviceForEpollLocked, so that both fds are added to epoll
+    for (std::unique_ptr<TouchVideoDevice>& videoDevice : mUnattachedVideoDevices) {
+        if (device->identifier.name == videoDevice->getName()) {
+            device->videoDevice = std::move(videoDevice);
+            break;
+        }
+    }
+    mUnattachedVideoDevices
+            .erase(std::remove_if(mUnattachedVideoDevices.begin(), mUnattachedVideoDevices.end(),
+                                  [](const std::unique_ptr<TouchVideoDevice>& videoDevice) {
+                                      return videoDevice == nullptr;
+                                  }),
+                   mUnattachedVideoDevices.end());
+
+    if (registerDeviceForEpollLocked(device) != OK) {
+        delete device;
+        return -1;
+    }
+
+    configureFd(device);
+
+    ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, "
+          "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, ",
+          deviceId, fd, devicePath, device->identifier.name.c_str(), device->classes,
+          device->configurationFile.c_str(), device->keyMap.keyLayoutFile.c_str(),
+          device->keyMap.keyCharacterMapFile.c_str(), toString(mBuiltInKeyboardId == deviceId));
+
+    addDeviceLocked(device);
+    return OK;
+}
+
+void EventHub::configureFd(Device* device) {
+    // Set fd parameters with ioctl, such as key repeat, suspend block, and clock type
+    if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+        // Disable kernel key repeat since we handle it ourselves
+        unsigned int repeatRate[] = {0, 0};
+        if (ioctl(device->fd, EVIOCSREP, repeatRate)) {
+            ALOGW("Unable to disable kernel key repeat for %s: %s", device->path.c_str(),
+                  strerror(errno));
+        }
+    }
+
+    // Tell the kernel that we want to use the monotonic clock for reporting timestamps
+    // associated with input events.  This is important because the input system
+    // uses the timestamps extensively and assumes they were recorded using the monotonic
+    // clock.
+    int clockId = CLOCK_MONOTONIC;
+    bool usingClockIoctl = !ioctl(device->fd, EVIOCSCLOCKID, &clockId);
+    ALOGI("usingClockIoctl=%s", toString(usingClockIoctl));
+}
+
+void EventHub::openVideoDeviceLocked(const std::string& devicePath) {
+    std::unique_ptr<TouchVideoDevice> videoDevice = TouchVideoDevice::create(devicePath);
+    if (!videoDevice) {
+        ALOGE("Could not create touch video device for %s. Ignoring", devicePath.c_str());
+        return;
+    }
+    // Transfer ownership of this video device to a matching input device
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        Device* device = mDevices.valueAt(i);
+        if (videoDevice->getName() == device->identifier.name) {
+            device->videoDevice = std::move(videoDevice);
+            if (device->enabled) {
+                registerVideoDeviceForEpollLocked(*device->videoDevice);
+            }
+            return;
+        }
+    }
+
+    // Couldn't find a matching input device, so just add it to a temporary holding queue.
+    // A matching input device may appear later.
+    ALOGI("Adding video device %s to list of unattached video devices",
+          videoDevice->getName().c_str());
+    mUnattachedVideoDevices.push_back(std::move(videoDevice));
+}
+
+bool EventHub::isDeviceEnabled(int32_t deviceId) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
+        return false;
+    }
+    return device->enabled;
+}
+
+status_t EventHub::enableDevice(int32_t deviceId) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
+        return BAD_VALUE;
+    }
+    if (device->enabled) {
+        ALOGW("Duplicate call to %s, input device %" PRId32 " already enabled", __func__, deviceId);
+        return OK;
+    }
+    status_t result = device->enable();
+    if (result != OK) {
+        ALOGE("Failed to enable device %" PRId32, deviceId);
+        return result;
+    }
+
+    configureFd(device);
+
+    return registerDeviceForEpollLocked(device);
+}
+
+status_t EventHub::disableDevice(int32_t deviceId) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
+        return BAD_VALUE;
+    }
+    if (!device->enabled) {
+        ALOGW("Duplicate call to %s, input device already disabled", __func__);
+        return OK;
+    }
+    unregisterDeviceFromEpollLocked(device);
+    return device->disable();
+}
+
+void EventHub::createVirtualKeyboardLocked() {
+    InputDeviceIdentifier identifier;
+    identifier.name = "Virtual";
+    identifier.uniqueId = "<virtual>";
+    assignDescriptorLocked(identifier);
+
+    Device* device =
+            new Device(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "<virtual>", identifier);
+    device->classes = INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY |
+            INPUT_DEVICE_CLASS_DPAD | INPUT_DEVICE_CLASS_VIRTUAL;
+    loadKeyMapLocked(device);
+    addDeviceLocked(device);
+}
+
+void EventHub::addDeviceLocked(Device* device) {
+    mDevices.add(device->id, device);
+    device->next = mOpeningDevices;
+    mOpeningDevices = device;
+}
+
+void EventHub::loadConfigurationLocked(Device* device) {
+    device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
+            device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
+    if (device->configurationFile.empty()) {
+        ALOGD("No input device configuration file found for device '%s'.",
+              device->identifier.name.c_str());
+    } else {
+        status_t status = PropertyMap::load(String8(device->configurationFile.c_str()),
+                                            &device->configuration);
+        if (status) {
+            ALOGE("Error loading input device configuration file for device '%s'.  "
+                  "Using default configuration.",
+                  device->identifier.name.c_str());
+        }
+    }
+}
+
+bool EventHub::loadVirtualKeyMapLocked(Device* device) {
+    // The virtual key map is supplied by the kernel as a system board property file.
+    std::string path;
+    path += "/sys/board_properties/virtualkeys.";
+    path += device->identifier.getCanonicalName();
+    if (access(path.c_str(), R_OK)) {
+        return false;
+    }
+    device->virtualKeyMap = VirtualKeyMap::load(path);
+    return device->virtualKeyMap != nullptr;
+}
+
+status_t EventHub::loadKeyMapLocked(Device* device) {
+    return device->keyMap.load(device->identifier, device->configuration);
+}
+
+bool EventHub::isExternalDeviceLocked(Device* device) {
+    if (device->configuration) {
+        bool value;
+        if (device->configuration->tryGetProperty(String8("device.internal"), value)) {
+            return !value;
+        }
+    }
+    return device->identifier.bus == BUS_USB || device->identifier.bus == BUS_BLUETOOTH;
+}
+
+bool EventHub::deviceHasMicLocked(Device* device) {
+    if (device->configuration) {
+        bool value;
+        if (device->configuration->tryGetProperty(String8("audio.mic"), value)) {
+            return value;
+        }
+    }
+    return false;
+}
+
+int32_t EventHub::getNextControllerNumberLocked(Device* device) {
+    if (mControllerNumbers.isFull()) {
+        ALOGI("Maximum number of controllers reached, assigning controller number 0 to device %s",
+              device->identifier.name.c_str());
+        return 0;
+    }
+    // Since the controller number 0 is reserved for non-controllers, translate all numbers up by
+    // one
+    return static_cast<int32_t>(mControllerNumbers.markFirstUnmarkedBit() + 1);
+}
+
+void EventHub::releaseControllerNumberLocked(Device* device) {
+    int32_t num = device->controllerNumber;
+    device->controllerNumber = 0;
+    if (num == 0) {
+        return;
+    }
+    mControllerNumbers.clearBit(static_cast<uint32_t>(num - 1));
+}
+
+void EventHub::setLedForControllerLocked(Device* device) {
+    for (int i = 0; i < MAX_CONTROLLER_LEDS; i++) {
+        setLedStateLocked(device, ALED_CONTROLLER_1 + i, device->controllerNumber == i + 1);
+    }
+}
+
+bool EventHub::hasKeycodeLocked(Device* device, int keycode) const {
+    if (!device->keyMap.haveKeyLayout()) {
+        return false;
+    }
+
+    std::vector<int32_t> scanCodes;
+    device->keyMap.keyLayoutMap->findScanCodesForKey(keycode, &scanCodes);
+    const size_t N = scanCodes.size();
+    for (size_t i = 0; i < N && i <= KEY_MAX; i++) {
+        int32_t sc = scanCodes[i];
+        if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+status_t EventHub::mapLed(Device* device, int32_t led, int32_t* outScanCode) const {
+    if (!device->keyMap.haveKeyLayout()) {
+        return NAME_NOT_FOUND;
+    }
+
+    int32_t scanCode;
+    if (device->keyMap.keyLayoutMap->findScanCodeForLed(led, &scanCode) != NAME_NOT_FOUND) {
+        if (scanCode >= 0 && scanCode <= LED_MAX && test_bit(scanCode, device->ledBitmask)) {
+            *outScanCode = scanCode;
+            return NO_ERROR;
+        }
+    }
+    return NAME_NOT_FOUND;
+}
+
+void EventHub::closeDeviceByPathLocked(const char* devicePath) {
+    Device* device = getDeviceByPathLocked(devicePath);
+    if (device) {
+        closeDeviceLocked(device);
+        return;
+    }
+    ALOGV("Remove device: %s not found, device may already have been removed.", devicePath);
+}
+
+/**
+ * Find the video device by filename, and close it.
+ * The video device is closed by path during an inotify event, where we don't have the
+ * additional context about the video device fd, or the associated input device.
+ */
+void EventHub::closeVideoDeviceByPathLocked(const std::string& devicePath) {
+    // A video device may be owned by an existing input device, or it may be stored in
+    // the mUnattachedVideoDevices queue. Check both locations.
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        Device* device = mDevices.valueAt(i);
+        if (device->videoDevice && device->videoDevice->getPath() == devicePath) {
+            unregisterVideoDeviceFromEpollLocked(*device->videoDevice);
+            device->videoDevice = nullptr;
+            return;
+        }
+    }
+    mUnattachedVideoDevices
+            .erase(std::remove_if(mUnattachedVideoDevices.begin(), mUnattachedVideoDevices.end(),
+                                  [&devicePath](
+                                          const std::unique_ptr<TouchVideoDevice>& videoDevice) {
+                                      return videoDevice->getPath() == devicePath;
+                                  }),
+                   mUnattachedVideoDevices.end());
+}
+
+void EventHub::closeAllDevicesLocked() {
+    mUnattachedVideoDevices.clear();
+    while (mDevices.size() > 0) {
+        closeDeviceLocked(mDevices.valueAt(mDevices.size() - 1));
+    }
+}
+
+void EventHub::closeDeviceLocked(Device* device) {
+    ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x", device->path.c_str(),
+          device->identifier.name.c_str(), device->id, device->fd, device->classes);
+
+    if (device->id == mBuiltInKeyboardId) {
+        ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
+              device->path.c_str(), mBuiltInKeyboardId);
+        mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD;
+    }
+
+    unregisterDeviceFromEpollLocked(device);
+    if (device->videoDevice) {
+        // This must be done after the video device is removed from epoll
+        mUnattachedVideoDevices.push_back(std::move(device->videoDevice));
+    }
+
+    releaseControllerNumberLocked(device);
+
+    mDevices.removeItem(device->id);
+    device->close();
+
+    // Unlink for opening devices list if it is present.
+    Device* pred = nullptr;
+    bool found = false;
+    for (Device* entry = mOpeningDevices; entry != nullptr;) {
+        if (entry == device) {
+            found = true;
+            break;
+        }
+        pred = entry;
+        entry = entry->next;
+    }
+    if (found) {
+        // Unlink the device from the opening devices list then delete it.
+        // We don't need to tell the client that the device was closed because
+        // it does not even know it was opened in the first place.
+        ALOGI("Device %s was immediately closed after opening.", device->path.c_str());
+        if (pred) {
+            pred->next = device->next;
+        } else {
+            mOpeningDevices = device->next;
+        }
+        delete device;
+    } else {
+        // Link into closing devices list.
+        // The device will be deleted later after we have informed the client.
+        device->next = mClosingDevices;
+        mClosingDevices = device;
+    }
+}
+
+status_t EventHub::readNotifyLocked() {
+    int res;
+    char event_buf[512];
+    int event_size;
+    int event_pos = 0;
+    struct inotify_event* event;
+
+    ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd);
+    res = read(mINotifyFd, event_buf, sizeof(event_buf));
+    if (res < (int)sizeof(*event)) {
+        if (errno == EINTR) return 0;
+        ALOGW("could not get event, %s\n", strerror(errno));
+        return -1;
+    }
+
+    while (res >= (int)sizeof(*event)) {
+        event = (struct inotify_event*)(event_buf + event_pos);
+        if (event->len) {
+            if (event->wd == mInputWd) {
+                std::string filename = StringPrintf("%s/%s", DEVICE_PATH, event->name);
+                if (event->mask & IN_CREATE) {
+                    openDeviceLocked(filename.c_str());
+                } else {
+                    ALOGI("Removing device '%s' due to inotify event\n", filename.c_str());
+                    closeDeviceByPathLocked(filename.c_str());
+                }
+            } else if (event->wd == mVideoWd) {
+                if (isV4lTouchNode(event->name)) {
+                    std::string filename = StringPrintf("%s/%s", VIDEO_DEVICE_PATH, event->name);
+                    if (event->mask & IN_CREATE) {
+                        openVideoDeviceLocked(filename);
+                    } else {
+                        ALOGI("Removing video device '%s' due to inotify event", filename.c_str());
+                        closeVideoDeviceByPathLocked(filename);
+                    }
+                }
+            } else {
+                LOG_ALWAYS_FATAL("Unexpected inotify event, wd = %i", event->wd);
+            }
+        }
+        event_size = sizeof(*event) + event->len;
+        res -= event_size;
+        event_pos += event_size;
+    }
+    return 0;
+}
+
+status_t EventHub::scanDirLocked(const char* dirname) {
+    char devname[PATH_MAX];
+    char* filename;
+    DIR* dir;
+    struct dirent* de;
+    dir = opendir(dirname);
+    if (dir == nullptr) 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;
+}
+
+/**
+ * Look for all dirname/v4l-touch* devices, and open them.
+ */
+status_t EventHub::scanVideoDirLocked(const std::string& dirname) {
+    DIR* dir;
+    struct dirent* de;
+    dir = opendir(dirname.c_str());
+    if (!dir) {
+        ALOGE("Could not open video directory %s", dirname.c_str());
+        return BAD_VALUE;
+    }
+
+    while ((de = readdir(dir))) {
+        const char* name = de->d_name;
+        if (isV4lTouchNode(name)) {
+            ALOGI("Found touch video device %s", name);
+            openVideoDeviceLocked(dirname + "/" + name);
+        }
+    }
+    closedir(dir);
+    return OK;
+}
+
+void EventHub::requestReopenDevices() {
+    ALOGV("requestReopenDevices() called");
+
+    AutoMutex _l(mLock);
+    mNeedToReopenDevices = true;
+}
+
+void EventHub::dump(std::string& dump) {
+    dump += "Event Hub State:\n";
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        dump += StringPrintf(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
+
+        dump += INDENT "Devices:\n";
+
+        for (size_t i = 0; i < mDevices.size(); i++) {
+            const Device* device = mDevices.valueAt(i);
+            if (mBuiltInKeyboardId == device->id) {
+                dump += StringPrintf(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n",
+                                     device->id, device->identifier.name.c_str());
+            } else {
+                dump += StringPrintf(INDENT2 "%d: %s\n", device->id,
+                                     device->identifier.name.c_str());
+            }
+            dump += StringPrintf(INDENT3 "Classes: 0x%08x\n", device->classes);
+            dump += StringPrintf(INDENT3 "Path: %s\n", device->path.c_str());
+            dump += StringPrintf(INDENT3 "Enabled: %s\n", toString(device->enabled));
+            dump += StringPrintf(INDENT3 "Descriptor: %s\n", device->identifier.descriptor.c_str());
+            dump += StringPrintf(INDENT3 "Location: %s\n", device->identifier.location.c_str());
+            dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber);
+            dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str());
+            dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
+                                         "product=0x%04x, version=0x%04x\n",
+                                 device->identifier.bus, device->identifier.vendor,
+                                 device->identifier.product, device->identifier.version);
+            dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n",
+                                 device->keyMap.keyLayoutFile.c_str());
+            dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n",
+                                 device->keyMap.keyCharacterMapFile.c_str());
+            dump += StringPrintf(INDENT3 "ConfigurationFile: %s\n",
+                                 device->configurationFile.c_str());
+            dump += StringPrintf(INDENT3 "HaveKeyboardLayoutOverlay: %s\n",
+                                 toString(device->overlayKeyMap != nullptr));
+            dump += INDENT3 "VideoDevice: ";
+            if (device->videoDevice) {
+                dump += device->videoDevice->dump() + "\n";
+            } else {
+                dump += "<none>\n";
+            }
+        }
+
+        dump += INDENT "Unattached video devices:\n";
+        for (const std::unique_ptr<TouchVideoDevice>& videoDevice : mUnattachedVideoDevices) {
+            dump += INDENT2 + videoDevice->dump() + "\n";
+        }
+        if (mUnattachedVideoDevices.empty()) {
+            dump += INDENT2 "<none>\n";
+        }
+    } // release lock
+}
+
+void EventHub::monitor() {
+    // Acquire and release the lock to ensure that the event hub has not deadlocked.
+    mLock.lock();
+    mLock.unlock();
+}
+
+}; // namespace android
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
new file mode 100644
index 0000000..521f621
--- /dev/null
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -0,0 +1,7461 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputReader"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages for each raw event received from the EventHub.
+#define DEBUG_RAW_EVENTS 0
+
+// Log debug messages about touch screen filtering hacks.
+#define DEBUG_HACKS 0
+
+// Log debug messages about virtual key processing.
+#define DEBUG_VIRTUAL_KEYS 0
+
+// Log debug messages about pointers.
+#define DEBUG_POINTERS 0
+
+// Log debug messages about pointer assignment calculations.
+#define DEBUG_POINTER_ASSIGNMENT 0
+
+// Log debug messages about gesture detection.
+#define DEBUG_GESTURES 0
+
+// Log debug messages about the vibrator.
+#define DEBUG_VIBRATOR 0
+
+// Log debug messages about fusing stylus data.
+#define DEBUG_STYLUS_FUSION 0
+
+#include "InputReader.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <log/log.h>
+
+#include <android-base/stringprintf.h>
+#include <input/Keyboard.h>
+#include <input/VirtualKeyMap.h>
+
+#define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+#define INDENT4 "        "
+#define INDENT5 "          "
+
+using android::base::StringPrintf;
+
+namespace android {
+
+// --- Constants ---
+
+// Maximum number of slots supported when using the slot-based Multitouch Protocol B.
+static constexpr size_t MAX_SLOTS = 32;
+
+// Maximum amount of latency to add to touch events while waiting for data from an
+// external stylus.
+static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72);
+
+// Maximum amount of time to wait on touch data before pushing out new pressure data.
+static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20);
+
+// Artificial latency on synthetic events created from stylus data without corresponding touch
+// data.
+static constexpr nsecs_t STYLUS_DATA_LATENCY = ms2ns(10);
+
+// --- Static Functions ---
+
+template <typename T>
+inline static T abs(const T& value) {
+    return value < 0 ? -value : value;
+}
+
+template <typename T>
+inline static T min(const T& a, const T& b) {
+    return a < b ? a : b;
+}
+
+template <typename T>
+inline static void swap(T& a, T& b) {
+    T temp = a;
+    a = b;
+    b = temp;
+}
+
+inline static float avg(float x, float y) {
+    return (x + y) / 2;
+}
+
+inline static float distance(float x1, float y1, float x2, float y2) {
+    return hypotf(x1 - x2, y1 - y2);
+}
+
+inline static int32_t signExtendNybble(int32_t value) {
+    return value >= 8 ? value - 16 : value;
+}
+
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation,
+                                           const int32_t map[][4], size_t mapSize) {
+    if (orientation != DISPLAY_ORIENTATION_0) {
+        for (size_t i = 0; i < mapSize; i++) {
+            if (value == map[i][0]) {
+                return map[i][orientation];
+            }
+        }
+    }
+    return value;
+}
+
+static const int32_t keyCodeRotationMap[][4] = {
+        // key codes enumerated counter-clockwise with the original (unrotated) key first
+        // no rotation,        90 degree rotation,  180 degree rotation, 270 degree rotation
+        {AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT},
+        {AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN},
+        {AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT},
+        {AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP},
+        {AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT,
+         AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT},
+        {AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP,
+         AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN},
+        {AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT,
+         AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT},
+        {AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN,
+         AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP},
+};
+static const size_t keyCodeRotationMapSize =
+        sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
+
+static int32_t rotateStemKey(int32_t value, int32_t orientation, const int32_t map[][2],
+                             size_t mapSize) {
+    if (orientation == DISPLAY_ORIENTATION_180) {
+        for (size_t i = 0; i < mapSize; i++) {
+            if (value == map[i][0]) {
+                return map[i][1];
+            }
+        }
+    }
+    return value;
+}
+
+// The mapping can be defined using input device configuration properties keyboard.rotated.stem_X
+static int32_t stemKeyRotationMap[][2] = {
+        // key codes enumerated with the original (unrotated) key first
+        // no rotation,           180 degree rotation
+        {AKEYCODE_STEM_PRIMARY, AKEYCODE_STEM_PRIMARY},
+        {AKEYCODE_STEM_1, AKEYCODE_STEM_1},
+        {AKEYCODE_STEM_2, AKEYCODE_STEM_2},
+        {AKEYCODE_STEM_3, AKEYCODE_STEM_3},
+};
+static const size_t stemKeyRotationMapSize =
+        sizeof(stemKeyRotationMap) / sizeof(stemKeyRotationMap[0]);
+
+static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
+    keyCode = rotateStemKey(keyCode, orientation, stemKeyRotationMap, stemKeyRotationMapSize);
+    return rotateValueUsingRotationMap(keyCode, orientation, keyCodeRotationMap,
+                                       keyCodeRotationMapSize);
+}
+
+static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) {
+    float temp;
+    switch (orientation) {
+        case DISPLAY_ORIENTATION_90:
+            temp = *deltaX;
+            *deltaX = *deltaY;
+            *deltaY = -temp;
+            break;
+
+        case DISPLAY_ORIENTATION_180:
+            *deltaX = -*deltaX;
+            *deltaY = -*deltaY;
+            break;
+
+        case DISPLAY_ORIENTATION_270:
+            temp = *deltaX;
+            *deltaX = -*deltaY;
+            *deltaY = temp;
+            break;
+    }
+}
+
+static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) {
+    return (sources & sourceMask & ~AINPUT_SOURCE_CLASS_MASK) != 0;
+}
+
+// Returns true if the pointer should be reported as being down given the specified
+// button states.  This determines whether the event is reported as a touch event.
+static bool isPointerDown(int32_t buttonState) {
+    return buttonState &
+            (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY |
+             AMOTION_EVENT_BUTTON_TERTIARY);
+}
+
+static float calculateCommonVector(float a, float b) {
+    if (a > 0 && b > 0) {
+        return a < b ? a : b;
+    } else if (a < 0 && b < 0) {
+        return a > b ? a : b;
+    } else {
+        return 0;
+    }
+}
+
+static void synthesizeButtonKey(InputReaderContext* context, int32_t action, nsecs_t when,
+                                int32_t deviceId, uint32_t source, int32_t displayId,
+                                uint32_t policyFlags, int32_t lastButtonState,
+                                int32_t currentButtonState, int32_t buttonState, int32_t keyCode) {
+    if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) &&
+         (currentButtonState & buttonState)) ||
+        (action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) &&
+         !(currentButtonState & buttonState))) {
+        NotifyKeyArgs args(context->getNextSequenceNum(), when, deviceId, source, displayId,
+                           policyFlags, action, 0, keyCode, 0, context->getGlobalMetaState(), when);
+        context->getListener()->notifyKey(&args);
+    }
+}
+
+static void synthesizeButtonKeys(InputReaderContext* context, int32_t action, nsecs_t when,
+                                 int32_t deviceId, uint32_t source, int32_t displayId,
+                                 uint32_t policyFlags, int32_t lastButtonState,
+                                 int32_t currentButtonState) {
+    synthesizeButtonKey(context, action, when, deviceId, source, displayId, policyFlags,
+                        lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_BACK,
+                        AKEYCODE_BACK);
+    synthesizeButtonKey(context, action, when, deviceId, source, displayId, policyFlags,
+                        lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_FORWARD,
+                        AKEYCODE_FORWARD);
+}
+
+// --- InputReader ---
+
+InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
+                         const sp<InputReaderPolicyInterface>& policy,
+                         const sp<InputListenerInterface>& listener)
+      : mContext(this),
+        mEventHub(eventHub),
+        mPolicy(policy),
+        mNextSequenceNum(1),
+        mGlobalMetaState(0),
+        mGeneration(1),
+        mDisableVirtualKeysTimeout(LLONG_MIN),
+        mNextTimeout(LLONG_MAX),
+        mConfigurationChangesToRefresh(0) {
+    mQueuedListener = new QueuedInputListener(listener);
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        refreshConfigurationLocked(0);
+        updateGlobalMetaStateLocked();
+    } // release lock
+}
+
+InputReader::~InputReader() {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        delete mDevices.valueAt(i);
+    }
+}
+
+void InputReader::loopOnce() {
+    int32_t oldGeneration;
+    int32_t timeoutMillis;
+    bool inputDevicesChanged = false;
+    std::vector<InputDeviceInfo> inputDevices;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        oldGeneration = mGeneration;
+        timeoutMillis = -1;
+
+        uint32_t changes = mConfigurationChangesToRefresh;
+        if (changes) {
+            mConfigurationChangesToRefresh = 0;
+            timeoutMillis = 0;
+            refreshConfigurationLocked(changes);
+        } else if (mNextTimeout != LLONG_MAX) {
+            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
+        }
+    } // release lock
+
+    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+        mReaderIsAliveCondition.broadcast();
+
+        if (count) {
+            processEventsLocked(mEventBuffer, count);
+        }
+
+        if (mNextTimeout != LLONG_MAX) {
+            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+            if (now >= mNextTimeout) {
+#if DEBUG_RAW_EVENTS
+                ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
+#endif
+                mNextTimeout = LLONG_MAX;
+                timeoutExpiredLocked(now);
+            }
+        }
+
+        if (oldGeneration != mGeneration) {
+            inputDevicesChanged = true;
+            getInputDevicesLocked(inputDevices);
+        }
+    } // release lock
+
+    // Send out a message that the describes the changed input devices.
+    if (inputDevicesChanged) {
+        mPolicy->notifyInputDevicesChanged(inputDevices);
+    }
+
+    // Flush queued events out to the listener.
+    // This must happen outside of the lock because the listener could potentially call
+    // back into the InputReader's methods, such as getScanCodeState, or become blocked
+    // on another thread similarly waiting to acquire the InputReader lock thereby
+    // resulting in a deadlock.  This situation is actually quite plausible because the
+    // listener is actually the input dispatcher, which calls into the window manager,
+    // which occasionally calls into the input reader.
+    mQueuedListener->flush();
+}
+
+void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
+    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 DEBUG_RAW_EVENTS
+            ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
+#endif
+            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;
+    }
+}
+
+void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex >= 0) {
+        ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+        return;
+    }
+
+    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
+    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+    int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);
+
+    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
+    device->configure(when, &mConfig, 0);
+    device->reset(when);
+
+    if (device->isIgnored()) {
+        ALOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId,
+              identifier.name.c_str());
+    } else {
+        ALOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId, identifier.name.c_str(),
+              device->getSources());
+    }
+
+    mDevices.add(deviceId, device);
+    bumpGenerationLocked();
+
+    if (device->getClasses() & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS) {
+        notifyExternalStylusPresenceChanged();
+    }
+}
+
+void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
+    InputDevice* device = nullptr;
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex < 0) {
+        ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
+        return;
+    }
+
+    device = mDevices.valueAt(deviceIndex);
+    mDevices.removeItemsAt(deviceIndex, 1);
+    bumpGenerationLocked();
+
+    if (device->isIgnored()) {
+        ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)", device->getId(),
+              device->getName().c_str());
+    } else {
+        ALOGI("Device removed: id=%d, name='%s', sources=0x%08x", device->getId(),
+              device->getName().c_str(), device->getSources());
+    }
+
+    if (device->getClasses() & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS) {
+        notifyExternalStylusPresenceChanged();
+    }
+
+    device->reset(when);
+    delete device;
+}
+
+InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
+                                             const InputDeviceIdentifier& identifier,
+                                             uint32_t classes) {
+    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
+                                          controllerNumber, identifier, classes);
+
+    // External devices.
+    if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
+        device->setExternal(true);
+    }
+
+    // Devices with mics.
+    if (classes & INPUT_DEVICE_CLASS_MIC) {
+        device->setMic(true);
+    }
+
+    // Switch-like devices.
+    if (classes & INPUT_DEVICE_CLASS_SWITCH) {
+        device->addMapper(new SwitchInputMapper(device));
+    }
+
+    // Scroll wheel-like devices.
+    if (classes & INPUT_DEVICE_CLASS_ROTARY_ENCODER) {
+        device->addMapper(new RotaryEncoderInputMapper(device));
+    }
+
+    // Vibrator-like devices.
+    if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
+        device->addMapper(new VibratorInputMapper(device));
+    }
+
+    // Keyboard-like devices.
+    uint32_t keyboardSource = 0;
+    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
+    }
+    if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
+        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+    }
+    if (classes & INPUT_DEVICE_CLASS_DPAD) {
+        keyboardSource |= AINPUT_SOURCE_DPAD;
+    }
+    if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+        keyboardSource |= AINPUT_SOURCE_GAMEPAD;
+    }
+
+    if (keyboardSource != 0) {
+        device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
+    }
+
+    // Cursor-like devices.
+    if (classes & INPUT_DEVICE_CLASS_CURSOR) {
+        device->addMapper(new CursorInputMapper(device));
+    }
+
+    // Touchscreens and touchpad devices.
+    if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
+        device->addMapper(new MultiTouchInputMapper(device));
+    } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
+        device->addMapper(new SingleTouchInputMapper(device));
+    }
+
+    // Joystick-like devices.
+    if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
+        device->addMapper(new JoystickInputMapper(device));
+    }
+
+    // External stylus-like devices.
+    if (classes & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS) {
+        device->addMapper(new ExternalStylusInputMapper(device));
+    }
+
+    return device;
+}
+
+void InputReader::processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents,
+                                               size_t count) {
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex < 0) {
+        ALOGW("Discarding event for unknown deviceId %d.", deviceId);
+        return;
+    }
+
+    InputDevice* device = mDevices.valueAt(deviceIndex);
+    if (device->isIgnored()) {
+        // ALOGD("Discarding event for ignored deviceId %d.", deviceId);
+        return;
+    }
+
+    device->process(rawEvents, count);
+}
+
+void InputReader::timeoutExpiredLocked(nsecs_t when) {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        if (!device->isIgnored()) {
+            device->timeoutExpired(when);
+        }
+    }
+}
+
+void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
+    // Reset global meta state because it depends on the list of all configured devices.
+    updateGlobalMetaStateLocked();
+
+    // Enqueue configuration changed.
+    NotifyConfigurationChangedArgs args(mContext.getNextSequenceNum(), when);
+    mQueuedListener->notifyConfigurationChanged(&args);
+}
+
+void InputReader::refreshConfigurationLocked(uint32_t changes) {
+    mPolicy->getReaderConfiguration(&mConfig);
+    mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
+
+    if (changes) {
+        ALOGI("Reconfiguring input devices, changes=%s",
+              InputReaderConfiguration::changesToString(changes).c_str());
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+        if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
+            mEventHub->requestReopenDevices();
+        } else {
+            for (size_t i = 0; i < mDevices.size(); i++) {
+                InputDevice* device = mDevices.valueAt(i);
+                device->configure(now, &mConfig, changes);
+            }
+        }
+    }
+}
+
+void InputReader::updateGlobalMetaStateLocked() {
+    mGlobalMetaState = 0;
+
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        mGlobalMetaState |= device->getMetaState();
+    }
+}
+
+int32_t InputReader::getGlobalMetaStateLocked() {
+    return mGlobalMetaState;
+}
+
+void InputReader::notifyExternalStylusPresenceChanged() {
+    refreshConfigurationLocked(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE);
+}
+
+void InputReader::getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices) {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        if (device->getClasses() & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS && !device->isIgnored()) {
+            InputDeviceInfo info;
+            device->getDeviceInfo(&info);
+            outDevices.push_back(info);
+        }
+    }
+}
+
+void InputReader::dispatchExternalStylusState(const StylusState& state) {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        device->updateExternalStylusState(state);
+    }
+}
+
+void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) {
+    mDisableVirtualKeysTimeout = time;
+}
+
+bool InputReader::shouldDropVirtualKeyLocked(nsecs_t now, InputDevice* device, int32_t keyCode,
+                                             int32_t scanCode) {
+    if (now < mDisableVirtualKeysTimeout) {
+        ALOGI("Dropping virtual key from device %s because virtual keys are "
+              "temporarily disabled for the next %0.3fms.  keyCode=%d, scanCode=%d",
+              device->getName().c_str(), (mDisableVirtualKeysTimeout - now) * 0.000001, keyCode,
+              scanCode);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void InputReader::fadePointerLocked() {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        device->fadePointer();
+    }
+}
+
+void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
+    if (when < mNextTimeout) {
+        mNextTimeout = when;
+        mEventHub->wake();
+    }
+}
+
+int32_t InputReader::bumpGenerationLocked() {
+    return ++mGeneration;
+}
+
+void InputReader::getInputDevices(std::vector<InputDeviceInfo>& outInputDevices) {
+    AutoMutex _l(mLock);
+    getInputDevicesLocked(outInputDevices);
+}
+
+void InputReader::getInputDevicesLocked(std::vector<InputDeviceInfo>& outInputDevices) {
+    outInputDevices.clear();
+
+    size_t numDevices = mDevices.size();
+    for (size_t i = 0; i < numDevices; i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        if (!device->isIgnored()) {
+            InputDeviceInfo info;
+            device->getDeviceInfo(&info);
+            outInputDevices.push_back(info);
+        }
+    }
+}
+
+int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) {
+    AutoMutex _l(mLock);
+
+    return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState);
+}
+
+int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) {
+    AutoMutex _l(mLock);
+
+    return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState);
+}
+
+int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
+    AutoMutex _l(mLock);
+
+    return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState);
+}
+
+int32_t InputReader::getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+                                    GetStateFunc getStateFunc) {
+    int32_t result = AKEY_STATE_UNKNOWN;
+    if (deviceId >= 0) {
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex >= 0) {
+            InputDevice* device = mDevices.valueAt(deviceIndex);
+            if (!device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                result = (device->*getStateFunc)(sourceMask, code);
+            }
+        }
+    } else {
+        size_t numDevices = mDevices.size();
+        for (size_t i = 0; i < numDevices; i++) {
+            InputDevice* device = mDevices.valueAt(i);
+            if (!device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                // If any device reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+                // value.  Otherwise, return AKEY_STATE_UP as long as one device reports it.
+                int32_t currentResult = (device->*getStateFunc)(sourceMask, code);
+                if (currentResult >= AKEY_STATE_DOWN) {
+                    return currentResult;
+                } else if (currentResult == AKEY_STATE_UP) {
+                    result = currentResult;
+                }
+            }
+        }
+    }
+    return result;
+}
+
+void InputReader::toggleCapsLockState(int32_t deviceId) {
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex < 0) {
+        ALOGW("Ignoring toggleCapsLock for unknown deviceId %" PRId32 ".", deviceId);
+        return;
+    }
+
+    InputDevice* device = mDevices.valueAt(deviceIndex);
+    if (device->isIgnored()) {
+        return;
+    }
+
+    device->updateMetaState(AKEYCODE_CAPS_LOCK);
+}
+
+bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+                          const int32_t* keyCodes, uint8_t* outFlags) {
+    AutoMutex _l(mLock);
+
+    memset(outFlags, 0, numCodes);
+    return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+}
+
+bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask,
+                                              size_t numCodes, const int32_t* keyCodes,
+                                              uint8_t* outFlags) {
+    bool result = false;
+    if (deviceId >= 0) {
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex >= 0) {
+            InputDevice* device = mDevices.valueAt(deviceIndex);
+            if (!device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                result = device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+            }
+        }
+    } else {
+        size_t numDevices = mDevices.size();
+        for (size_t i = 0; i < numDevices; i++) {
+            InputDevice* device = mDevices.valueAt(i);
+            if (!device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                result |= device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+            }
+        }
+    }
+    return result;
+}
+
+void InputReader::requestRefreshConfiguration(uint32_t changes) {
+    AutoMutex _l(mLock);
+
+    if (changes) {
+        bool needWake = !mConfigurationChangesToRefresh;
+        mConfigurationChangesToRefresh |= changes;
+
+        if (needWake) {
+            mEventHub->wake();
+        }
+    }
+}
+
+void InputReader::vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+                          ssize_t repeat, int32_t token) {
+    AutoMutex _l(mLock);
+
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex >= 0) {
+        InputDevice* device = mDevices.valueAt(deviceIndex);
+        device->vibrate(pattern, patternSize, repeat, token);
+    }
+}
+
+void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
+    AutoMutex _l(mLock);
+
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex >= 0) {
+        InputDevice* device = mDevices.valueAt(deviceIndex);
+        device->cancelVibrate(token);
+    }
+}
+
+bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
+    AutoMutex _l(mLock);
+
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex >= 0) {
+        InputDevice* device = mDevices.valueAt(deviceIndex);
+        return device->isEnabled();
+    }
+    ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId);
+    return false;
+}
+
+bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
+    AutoMutex _l(mLock);
+
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex < 0) {
+        ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId);
+        return false;
+    }
+
+    InputDevice* device = mDevices.valueAt(deviceIndex);
+    if (!device->isEnabled()) {
+        ALOGW("Ignoring disabled device %s", device->getName().c_str());
+        return false;
+    }
+
+    std::optional<int32_t> associatedDisplayId = device->getAssociatedDisplayId();
+    // No associated display. By default, can dispatch to all displays.
+    if (!associatedDisplayId) {
+        return true;
+    }
+
+    if (*associatedDisplayId == ADISPLAY_ID_NONE) {
+        ALOGW("Device %s is associated with display ADISPLAY_ID_NONE.", device->getName().c_str());
+        return true;
+    }
+
+    return *associatedDisplayId == displayId;
+}
+
+void InputReader::dump(std::string& dump) {
+    AutoMutex _l(mLock);
+
+    mEventHub->dump(dump);
+    dump += "\n";
+
+    dump += "Input Reader State:\n";
+
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        mDevices.valueAt(i)->dump(dump);
+    }
+
+    dump += INDENT "Configuration:\n";
+    dump += INDENT2 "ExcludedDeviceNames: [";
+    for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) {
+        if (i != 0) {
+            dump += ", ";
+        }
+        dump += mConfig.excludedDeviceNames[i];
+    }
+    dump += "]\n";
+    dump += StringPrintf(INDENT2 "VirtualKeyQuietTime: %0.1fms\n",
+                         mConfig.virtualKeyQuietTime * 0.000001f);
+
+    dump += StringPrintf(INDENT2 "PointerVelocityControlParameters: "
+                                 "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, "
+                                 "acceleration=%0.3f\n",
+                         mConfig.pointerVelocityControlParameters.scale,
+                         mConfig.pointerVelocityControlParameters.lowThreshold,
+                         mConfig.pointerVelocityControlParameters.highThreshold,
+                         mConfig.pointerVelocityControlParameters.acceleration);
+
+    dump += StringPrintf(INDENT2 "WheelVelocityControlParameters: "
+                                 "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, "
+                                 "acceleration=%0.3f\n",
+                         mConfig.wheelVelocityControlParameters.scale,
+                         mConfig.wheelVelocityControlParameters.lowThreshold,
+                         mConfig.wheelVelocityControlParameters.highThreshold,
+                         mConfig.wheelVelocityControlParameters.acceleration);
+
+    dump += StringPrintf(INDENT2 "PointerGesture:\n");
+    dump += StringPrintf(INDENT3 "Enabled: %s\n", toString(mConfig.pointerGesturesEnabled));
+    dump += StringPrintf(INDENT3 "QuietInterval: %0.1fms\n",
+                         mConfig.pointerGestureQuietInterval * 0.000001f);
+    dump += StringPrintf(INDENT3 "DragMinSwitchSpeed: %0.1fpx/s\n",
+                         mConfig.pointerGestureDragMinSwitchSpeed);
+    dump += StringPrintf(INDENT3 "TapInterval: %0.1fms\n",
+                         mConfig.pointerGestureTapInterval * 0.000001f);
+    dump += StringPrintf(INDENT3 "TapDragInterval: %0.1fms\n",
+                         mConfig.pointerGestureTapDragInterval * 0.000001f);
+    dump += StringPrintf(INDENT3 "TapSlop: %0.1fpx\n", mConfig.pointerGestureTapSlop);
+    dump += StringPrintf(INDENT3 "MultitouchSettleInterval: %0.1fms\n",
+                         mConfig.pointerGestureMultitouchSettleInterval * 0.000001f);
+    dump += StringPrintf(INDENT3 "MultitouchMinDistance: %0.1fpx\n",
+                         mConfig.pointerGestureMultitouchMinDistance);
+    dump += StringPrintf(INDENT3 "SwipeTransitionAngleCosine: %0.1f\n",
+                         mConfig.pointerGestureSwipeTransitionAngleCosine);
+    dump += StringPrintf(INDENT3 "SwipeMaxWidthRatio: %0.1f\n",
+                         mConfig.pointerGestureSwipeMaxWidthRatio);
+    dump += StringPrintf(INDENT3 "MovementSpeedRatio: %0.1f\n",
+                         mConfig.pointerGestureMovementSpeedRatio);
+    dump += StringPrintf(INDENT3 "ZoomSpeedRatio: %0.1f\n", mConfig.pointerGestureZoomSpeedRatio);
+
+    dump += INDENT3 "Viewports:\n";
+    mConfig.dump(dump);
+}
+
+void InputReader::monitor() {
+    // Acquire and release the lock to ensure that the reader has not deadlocked.
+    mLock.lock();
+    mEventHub->wake();
+    mReaderIsAliveCondition.wait(mLock);
+    mLock.unlock();
+
+    // Check the EventHub
+    mEventHub->monitor();
+}
+
+// --- InputReader::ContextImpl ---
+
+InputReader::ContextImpl::ContextImpl(InputReader* reader) : mReader(reader) {}
+
+void InputReader::ContextImpl::updateGlobalMetaState() {
+    // lock is already held by the input loop
+    mReader->updateGlobalMetaStateLocked();
+}
+
+int32_t InputReader::ContextImpl::getGlobalMetaState() {
+    // lock is already held by the input loop
+    return mReader->getGlobalMetaStateLocked();
+}
+
+void InputReader::ContextImpl::disableVirtualKeysUntil(nsecs_t time) {
+    // lock is already held by the input loop
+    mReader->disableVirtualKeysUntilLocked(time);
+}
+
+bool InputReader::ContextImpl::shouldDropVirtualKey(nsecs_t now, InputDevice* device,
+                                                    int32_t keyCode, int32_t scanCode) {
+    // lock is already held by the input loop
+    return mReader->shouldDropVirtualKeyLocked(now, device, keyCode, scanCode);
+}
+
+void InputReader::ContextImpl::fadePointer() {
+    // lock is already held by the input loop
+    mReader->fadePointerLocked();
+}
+
+void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) {
+    // lock is already held by the input loop
+    mReader->requestTimeoutAtTimeLocked(when);
+}
+
+int32_t InputReader::ContextImpl::bumpGeneration() {
+    // lock is already held by the input loop
+    return mReader->bumpGenerationLocked();
+}
+
+void InputReader::ContextImpl::getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) {
+    // lock is already held by whatever called refreshConfigurationLocked
+    mReader->getExternalStylusDevicesLocked(outDevices);
+}
+
+void InputReader::ContextImpl::dispatchExternalStylusState(const StylusState& state) {
+    mReader->dispatchExternalStylusState(state);
+}
+
+InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() {
+    return mReader->mPolicy.get();
+}
+
+InputListenerInterface* InputReader::ContextImpl::getListener() {
+    return mReader->mQueuedListener.get();
+}
+
+EventHubInterface* InputReader::ContextImpl::getEventHub() {
+    return mReader->mEventHub.get();
+}
+
+uint32_t InputReader::ContextImpl::getNextSequenceNum() {
+    return (mReader->mNextSequenceNum)++;
+}
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+                         int32_t controllerNumber, const InputDeviceIdentifier& identifier,
+                         uint32_t classes)
+      : mContext(context),
+        mId(id),
+        mGeneration(generation),
+        mControllerNumber(controllerNumber),
+        mIdentifier(identifier),
+        mClasses(classes),
+        mSources(0),
+        mIsExternal(false),
+        mHasMic(false),
+        mDropUntilNextSync(false) {}
+
+InputDevice::~InputDevice() {
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        delete mMappers[i];
+    }
+    mMappers.clear();
+}
+
+bool InputDevice::isEnabled() {
+    return getEventHub()->isDeviceEnabled(mId);
+}
+
+void InputDevice::setEnabled(bool enabled, nsecs_t when) {
+    if (enabled && mAssociatedDisplayPort && !mAssociatedViewport) {
+        ALOGW("Cannot enable input device %s because it is associated with port %" PRIu8 ", "
+              "but the corresponding viewport is not found",
+              getName().c_str(), *mAssociatedDisplayPort);
+        enabled = false;
+    }
+
+    if (isEnabled() == enabled) {
+        return;
+    }
+
+    if (enabled) {
+        getEventHub()->enableDevice(mId);
+        reset(when);
+    } else {
+        reset(when);
+        getEventHub()->disableDevice(mId);
+    }
+    // Must change generation to flag this device as changed
+    bumpGeneration();
+}
+
+void InputDevice::dump(std::string& dump) {
+    InputDeviceInfo deviceInfo;
+    getDeviceInfo(&deviceInfo);
+
+    dump += StringPrintf(INDENT "Device %d: %s\n", deviceInfo.getId(),
+                         deviceInfo.getDisplayName().c_str());
+    dump += StringPrintf(INDENT2 "Generation: %d\n", mGeneration);
+    dump += StringPrintf(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
+    dump += StringPrintf(INDENT2 "AssociatedDisplayPort: ");
+    if (mAssociatedDisplayPort) {
+        dump += StringPrintf("%" PRIu8 "\n", *mAssociatedDisplayPort);
+    } else {
+        dump += "<none>\n";
+    }
+    dump += StringPrintf(INDENT2 "HasMic:     %s\n", toString(mHasMic));
+    dump += StringPrintf(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+    dump += StringPrintf(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+
+    const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
+    if (!ranges.empty()) {
+        dump += INDENT2 "Motion Ranges:\n";
+        for (size_t i = 0; i < ranges.size(); i++) {
+            const InputDeviceInfo::MotionRange& range = ranges[i];
+            const char* label = getAxisLabel(range.axis);
+            char name[32];
+            if (label) {
+                strncpy(name, label, sizeof(name));
+                name[sizeof(name) - 1] = '\0';
+            } else {
+                snprintf(name, sizeof(name), "%d", range.axis);
+            }
+            dump += StringPrintf(INDENT3
+                                 "%s: source=0x%08x, "
+                                 "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, resolution=%0.3f\n",
+                                 name, range.source, range.min, range.max, range.flat, range.fuzz,
+                                 range.resolution);
+        }
+    }
+
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->dump(dump);
+    }
+}
+
+void InputDevice::addMapper(InputMapper* mapper) {
+    mMappers.push_back(mapper);
+}
+
+void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
+                            uint32_t changes) {
+    mSources = 0;
+
+    if (!isIgnored()) {
+        if (!changes) { // first time only
+            mContext->getEventHub()->getConfiguration(mId, &mConfiguration);
+        }
+
+        if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
+            if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+                sp<KeyCharacterMap> keyboardLayout =
+                        mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
+                if (mContext->getEventHub()->setKeyboardLayoutOverlay(mId, keyboardLayout)) {
+                    bumpGeneration();
+                }
+            }
+        }
+
+        if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) {
+            if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+                std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
+                if (mAlias != alias) {
+                    mAlias = alias;
+                    bumpGeneration();
+                }
+            }
+        }
+
+        if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) {
+            auto it = config->disabledDevices.find(mId);
+            bool enabled = it == config->disabledDevices.end();
+            setEnabled(enabled, when);
+        }
+
+        if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+            // In most situations, no port will be specified.
+            mAssociatedDisplayPort = std::nullopt;
+            mAssociatedViewport = std::nullopt;
+            // Find the display port that corresponds to the current input port.
+            const std::string& inputPort = mIdentifier.location;
+            if (!inputPort.empty()) {
+                const std::unordered_map<std::string, uint8_t>& ports = config->portAssociations;
+                const auto& displayPort = ports.find(inputPort);
+                if (displayPort != ports.end()) {
+                    mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                }
+            }
+
+            // If the device was explicitly disabled by the user, it would be present in the
+            // "disabledDevices" list. If it is associated with a specific display, and it was not
+            // explicitly disabled, then enable/disable the device based on whether we can find the
+            // corresponding viewport.
+            bool enabled = (config->disabledDevices.find(mId) == config->disabledDevices.end());
+            if (mAssociatedDisplayPort) {
+                mAssociatedViewport = config->getDisplayViewportByPort(*mAssociatedDisplayPort);
+                if (!mAssociatedViewport) {
+                    ALOGW("Input device %s should be associated with display on port %" PRIu8 ", "
+                          "but the corresponding viewport is not found.",
+                          getName().c_str(), *mAssociatedDisplayPort);
+                    enabled = false;
+                }
+            }
+
+            if (changes) {
+                // For first-time configuration, only allow device to be disabled after mappers have
+                // finished configuring. This is because we need to read some of the properties from
+                // the device's open fd.
+                setEnabled(enabled, when);
+            }
+        }
+
+        for (InputMapper* mapper : mMappers) {
+            mapper->configure(when, config, changes);
+            mSources |= mapper->getSources();
+        }
+
+        // If a device is just plugged but it might be disabled, we need to update some info like
+        // axis range of touch from each InputMapper first, then disable it.
+        if (!changes) {
+            setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(), when);
+        }
+    }
+}
+
+void InputDevice::reset(nsecs_t when) {
+    for (InputMapper* mapper : mMappers) {
+        mapper->reset(when);
+    }
+
+    mContext->updateGlobalMetaState();
+
+    notifyReset(when);
+}
+
+void InputDevice::process(const RawEvent* rawEvents, size_t count) {
+    // Process all of the events in order for each mapper.
+    // We cannot simply ask each mapper to process them in bulk because mappers may
+    // have side-effects that must be interleaved.  For example, joystick movement events and
+    // gamepad button presses are handled by different mappers but they should be dispatched
+    // in the order received.
+    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
+#if DEBUG_RAW_EVENTS
+        ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64,
+              rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value, rawEvent->when);
+#endif
+
+        if (mDropUntilNextSync) {
+            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+                mDropUntilNextSync = false;
+#if DEBUG_RAW_EVENTS
+                ALOGD("Recovered from input event buffer overrun.");
+#endif
+            } else {
+#if DEBUG_RAW_EVENTS
+                ALOGD("Dropped input event while waiting for next input sync.");
+#endif
+            }
+        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
+            ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
+            mDropUntilNextSync = true;
+            reset(rawEvent->when);
+        } else {
+            for (InputMapper* mapper : mMappers) {
+                mapper->process(rawEvent);
+            }
+        }
+        --count;
+    }
+}
+
+void InputDevice::timeoutExpired(nsecs_t when) {
+    for (InputMapper* mapper : mMappers) {
+        mapper->timeoutExpired(when);
+    }
+}
+
+void InputDevice::updateExternalStylusState(const StylusState& state) {
+    for (InputMapper* mapper : mMappers) {
+        mapper->updateExternalStylusState(state);
+    }
+}
+
+void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
+    outDeviceInfo->initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
+                              mHasMic);
+    for (InputMapper* mapper : mMappers) {
+        mapper->populateDeviceInfo(outDeviceInfo);
+    }
+}
+
+int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    return getState(sourceMask, keyCode, &InputMapper::getKeyCodeState);
+}
+
+int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    return getState(sourceMask, scanCode, &InputMapper::getScanCodeState);
+}
+
+int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+    return getState(sourceMask, switchCode, &InputMapper::getSwitchState);
+}
+
+int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) {
+    int32_t result = AKEY_STATE_UNKNOWN;
+    for (InputMapper* mapper : mMappers) {
+        if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+            // If any mapper reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+            // value.  Otherwise, return AKEY_STATE_UP as long as one mapper reports it.
+            int32_t currentResult = (mapper->*getStateFunc)(sourceMask, code);
+            if (currentResult >= AKEY_STATE_DOWN) {
+                return currentResult;
+            } else if (currentResult == AKEY_STATE_UP) {
+                result = currentResult;
+            }
+        }
+    }
+    return result;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+                                        const int32_t* keyCodes, uint8_t* outFlags) {
+    bool result = false;
+    for (InputMapper* mapper : mMappers) {
+        if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+            result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+        }
+    }
+    return result;
+}
+
+void InputDevice::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+                          int32_t token) {
+    for (InputMapper* mapper : mMappers) {
+        mapper->vibrate(pattern, patternSize, repeat, token);
+    }
+}
+
+void InputDevice::cancelVibrate(int32_t token) {
+    for (InputMapper* mapper : mMappers) {
+        mapper->cancelVibrate(token);
+    }
+}
+
+void InputDevice::cancelTouch(nsecs_t when) {
+    for (InputMapper* mapper : mMappers) {
+        mapper->cancelTouch(when);
+    }
+}
+
+int32_t InputDevice::getMetaState() {
+    int32_t result = 0;
+    for (InputMapper* mapper : mMappers) {
+        result |= mapper->getMetaState();
+    }
+    return result;
+}
+
+void InputDevice::updateMetaState(int32_t keyCode) {
+    for (InputMapper* mapper : mMappers) {
+        mapper->updateMetaState(keyCode);
+    }
+}
+
+void InputDevice::fadePointer() {
+    for (InputMapper* mapper : mMappers) {
+        mapper->fadePointer();
+    }
+}
+
+void InputDevice::bumpGeneration() {
+    mGeneration = mContext->bumpGeneration();
+}
+
+void InputDevice::notifyReset(nsecs_t when) {
+    NotifyDeviceResetArgs args(mContext->getNextSequenceNum(), when, mId);
+    mContext->getListener()->notifyDeviceReset(&args);
+}
+
+std::optional<int32_t> InputDevice::getAssociatedDisplayId() {
+    // Check if we had associated to the specific display.
+    if (mAssociatedViewport) {
+        return mAssociatedViewport->displayId;
+    }
+
+    // No associated display port, check if some InputMapper is associated.
+    for (InputMapper* mapper : mMappers) {
+        std::optional<int32_t> associatedDisplayId = mapper->getAssociatedDisplayId();
+        if (associatedDisplayId) {
+            return associatedDisplayId;
+        }
+    }
+
+    return std::nullopt;
+}
+
+// --- CursorButtonAccumulator ---
+
+CursorButtonAccumulator::CursorButtonAccumulator() {
+    clearButtons();
+}
+
+void CursorButtonAccumulator::reset(InputDevice* device) {
+    mBtnLeft = device->isKeyPressed(BTN_LEFT);
+    mBtnRight = device->isKeyPressed(BTN_RIGHT);
+    mBtnMiddle = device->isKeyPressed(BTN_MIDDLE);
+    mBtnBack = device->isKeyPressed(BTN_BACK);
+    mBtnSide = device->isKeyPressed(BTN_SIDE);
+    mBtnForward = device->isKeyPressed(BTN_FORWARD);
+    mBtnExtra = device->isKeyPressed(BTN_EXTRA);
+    mBtnTask = device->isKeyPressed(BTN_TASK);
+}
+
+void CursorButtonAccumulator::clearButtons() {
+    mBtnLeft = 0;
+    mBtnRight = 0;
+    mBtnMiddle = 0;
+    mBtnBack = 0;
+    mBtnSide = 0;
+    mBtnForward = 0;
+    mBtnExtra = 0;
+    mBtnTask = 0;
+}
+
+void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_KEY) {
+        switch (rawEvent->code) {
+            case BTN_LEFT:
+                mBtnLeft = rawEvent->value;
+                break;
+            case BTN_RIGHT:
+                mBtnRight = rawEvent->value;
+                break;
+            case BTN_MIDDLE:
+                mBtnMiddle = rawEvent->value;
+                break;
+            case BTN_BACK:
+                mBtnBack = rawEvent->value;
+                break;
+            case BTN_SIDE:
+                mBtnSide = rawEvent->value;
+                break;
+            case BTN_FORWARD:
+                mBtnForward = rawEvent->value;
+                break;
+            case BTN_EXTRA:
+                mBtnExtra = rawEvent->value;
+                break;
+            case BTN_TASK:
+                mBtnTask = rawEvent->value;
+                break;
+        }
+    }
+}
+
+uint32_t CursorButtonAccumulator::getButtonState() const {
+    uint32_t result = 0;
+    if (mBtnLeft) {
+        result |= AMOTION_EVENT_BUTTON_PRIMARY;
+    }
+    if (mBtnRight) {
+        result |= AMOTION_EVENT_BUTTON_SECONDARY;
+    }
+    if (mBtnMiddle) {
+        result |= AMOTION_EVENT_BUTTON_TERTIARY;
+    }
+    if (mBtnBack || mBtnSide) {
+        result |= AMOTION_EVENT_BUTTON_BACK;
+    }
+    if (mBtnForward || mBtnExtra) {
+        result |= AMOTION_EVENT_BUTTON_FORWARD;
+    }
+    return result;
+}
+
+// --- CursorMotionAccumulator ---
+
+CursorMotionAccumulator::CursorMotionAccumulator() {
+    clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::reset(InputDevice* device) {
+    clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::clearRelativeAxes() {
+    mRelX = 0;
+    mRelY = 0;
+}
+
+void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_REL) {
+        switch (rawEvent->code) {
+            case REL_X:
+                mRelX = rawEvent->value;
+                break;
+            case REL_Y:
+                mRelY = rawEvent->value;
+                break;
+        }
+    }
+}
+
+void CursorMotionAccumulator::finishSync() {
+    clearRelativeAxes();
+}
+
+// --- CursorScrollAccumulator ---
+
+CursorScrollAccumulator::CursorScrollAccumulator() : mHaveRelWheel(false), mHaveRelHWheel(false) {
+    clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::configure(InputDevice* device) {
+    mHaveRelWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_WHEEL);
+    mHaveRelHWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_HWHEEL);
+}
+
+void CursorScrollAccumulator::reset(InputDevice* device) {
+    clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::clearRelativeAxes() {
+    mRelWheel = 0;
+    mRelHWheel = 0;
+}
+
+void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_REL) {
+        switch (rawEvent->code) {
+            case REL_WHEEL:
+                mRelWheel = rawEvent->value;
+                break;
+            case REL_HWHEEL:
+                mRelHWheel = rawEvent->value;
+                break;
+        }
+    }
+}
+
+void CursorScrollAccumulator::finishSync() {
+    clearRelativeAxes();
+}
+
+// --- TouchButtonAccumulator ---
+
+TouchButtonAccumulator::TouchButtonAccumulator() : mHaveBtnTouch(false), mHaveStylus(false) {
+    clearButtons();
+}
+
+void TouchButtonAccumulator::configure(InputDevice* device) {
+    mHaveBtnTouch = device->hasKey(BTN_TOUCH);
+    mHaveStylus = device->hasKey(BTN_TOOL_PEN) || device->hasKey(BTN_TOOL_RUBBER) ||
+            device->hasKey(BTN_TOOL_BRUSH) || device->hasKey(BTN_TOOL_PENCIL) ||
+            device->hasKey(BTN_TOOL_AIRBRUSH);
+}
+
+void TouchButtonAccumulator::reset(InputDevice* device) {
+    mBtnTouch = device->isKeyPressed(BTN_TOUCH);
+    mBtnStylus = device->isKeyPressed(BTN_STYLUS);
+    // BTN_0 is what gets mapped for the HID usage Digitizers.SecondaryBarrelSwitch
+    mBtnStylus2 = device->isKeyPressed(BTN_STYLUS2) || device->isKeyPressed(BTN_0);
+    mBtnToolFinger = device->isKeyPressed(BTN_TOOL_FINGER);
+    mBtnToolPen = device->isKeyPressed(BTN_TOOL_PEN);
+    mBtnToolRubber = device->isKeyPressed(BTN_TOOL_RUBBER);
+    mBtnToolBrush = device->isKeyPressed(BTN_TOOL_BRUSH);
+    mBtnToolPencil = device->isKeyPressed(BTN_TOOL_PENCIL);
+    mBtnToolAirbrush = device->isKeyPressed(BTN_TOOL_AIRBRUSH);
+    mBtnToolMouse = device->isKeyPressed(BTN_TOOL_MOUSE);
+    mBtnToolLens = device->isKeyPressed(BTN_TOOL_LENS);
+    mBtnToolDoubleTap = device->isKeyPressed(BTN_TOOL_DOUBLETAP);
+    mBtnToolTripleTap = device->isKeyPressed(BTN_TOOL_TRIPLETAP);
+    mBtnToolQuadTap = device->isKeyPressed(BTN_TOOL_QUADTAP);
+}
+
+void TouchButtonAccumulator::clearButtons() {
+    mBtnTouch = 0;
+    mBtnStylus = 0;
+    mBtnStylus2 = 0;
+    mBtnToolFinger = 0;
+    mBtnToolPen = 0;
+    mBtnToolRubber = 0;
+    mBtnToolBrush = 0;
+    mBtnToolPencil = 0;
+    mBtnToolAirbrush = 0;
+    mBtnToolMouse = 0;
+    mBtnToolLens = 0;
+    mBtnToolDoubleTap = 0;
+    mBtnToolTripleTap = 0;
+    mBtnToolQuadTap = 0;
+}
+
+void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_KEY) {
+        switch (rawEvent->code) {
+            case BTN_TOUCH:
+                mBtnTouch = rawEvent->value;
+                break;
+            case BTN_STYLUS:
+                mBtnStylus = rawEvent->value;
+                break;
+            case BTN_STYLUS2:
+            case BTN_0: // BTN_0 is what gets mapped for the HID usage
+                        // Digitizers.SecondaryBarrelSwitch
+                mBtnStylus2 = rawEvent->value;
+                break;
+            case BTN_TOOL_FINGER:
+                mBtnToolFinger = rawEvent->value;
+                break;
+            case BTN_TOOL_PEN:
+                mBtnToolPen = rawEvent->value;
+                break;
+            case BTN_TOOL_RUBBER:
+                mBtnToolRubber = rawEvent->value;
+                break;
+            case BTN_TOOL_BRUSH:
+                mBtnToolBrush = rawEvent->value;
+                break;
+            case BTN_TOOL_PENCIL:
+                mBtnToolPencil = rawEvent->value;
+                break;
+            case BTN_TOOL_AIRBRUSH:
+                mBtnToolAirbrush = rawEvent->value;
+                break;
+            case BTN_TOOL_MOUSE:
+                mBtnToolMouse = rawEvent->value;
+                break;
+            case BTN_TOOL_LENS:
+                mBtnToolLens = rawEvent->value;
+                break;
+            case BTN_TOOL_DOUBLETAP:
+                mBtnToolDoubleTap = rawEvent->value;
+                break;
+            case BTN_TOOL_TRIPLETAP:
+                mBtnToolTripleTap = rawEvent->value;
+                break;
+            case BTN_TOOL_QUADTAP:
+                mBtnToolQuadTap = rawEvent->value;
+                break;
+        }
+    }
+}
+
+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;
+}
+
+int32_t TouchButtonAccumulator::getToolType() const {
+    if (mBtnToolMouse || mBtnToolLens) {
+        return AMOTION_EVENT_TOOL_TYPE_MOUSE;
+    }
+    if (mBtnToolRubber) {
+        return AMOTION_EVENT_TOOL_TYPE_ERASER;
+    }
+    if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) {
+        return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    }
+    if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) {
+        return AMOTION_EVENT_TOOL_TYPE_FINGER;
+    }
+    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+bool TouchButtonAccumulator::isToolActive() const {
+    return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber || mBtnToolBrush ||
+            mBtnToolPencil || mBtnToolAirbrush || mBtnToolMouse || mBtnToolLens ||
+            mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap;
+}
+
+bool TouchButtonAccumulator::isHovering() const {
+    return mHaveBtnTouch && !mBtnTouch;
+}
+
+bool TouchButtonAccumulator::hasStylus() const {
+    return mHaveStylus;
+}
+
+// --- RawPointerAxes ---
+
+RawPointerAxes::RawPointerAxes() {
+    clear();
+}
+
+void RawPointerAxes::clear() {
+    x.clear();
+    y.clear();
+    pressure.clear();
+    touchMajor.clear();
+    touchMinor.clear();
+    toolMajor.clear();
+    toolMinor.clear();
+    orientation.clear();
+    distance.clear();
+    tiltX.clear();
+    tiltY.clear();
+    trackingId.clear();
+    slot.clear();
+}
+
+// --- RawPointerData ---
+
+RawPointerData::RawPointerData() {
+    clear();
+}
+
+void RawPointerData::clear() {
+    pointerCount = 0;
+    clearIdBits();
+}
+
+void RawPointerData::copyFrom(const RawPointerData& other) {
+    pointerCount = other.pointerCount;
+    hoveringIdBits = other.hoveringIdBits;
+    touchingIdBits = other.touchingIdBits;
+
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointers[i] = other.pointers[i];
+
+        int id = pointers[i].id;
+        idToIndex[id] = other.idToIndex[id];
+    }
+}
+
+void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
+    float x = 0, y = 0;
+    uint32_t count = touchingIdBits.count();
+    if (count) {
+        for (BitSet32 idBits(touchingIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            const Pointer& pointer = pointerForId(id);
+            x += pointer.x;
+            y += pointer.y;
+        }
+        x /= count;
+        y /= count;
+    }
+    *outX = x;
+    *outY = y;
+}
+
+// --- CookedPointerData ---
+
+CookedPointerData::CookedPointerData() {
+    clear();
+}
+
+void CookedPointerData::clear() {
+    pointerCount = 0;
+    hoveringIdBits.clear();
+    touchingIdBits.clear();
+}
+
+void CookedPointerData::copyFrom(const CookedPointerData& other) {
+    pointerCount = other.pointerCount;
+    hoveringIdBits = other.hoveringIdBits;
+    touchingIdBits = other.touchingIdBits;
+
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].copyFrom(other.pointerProperties[i]);
+        pointerCoords[i].copyFrom(other.pointerCoords[i]);
+
+        int id = pointerProperties[i].id;
+        idToIndex[id] = other.idToIndex[id];
+    }
+}
+
+// --- SingleTouchMotionAccumulator ---
+
+SingleTouchMotionAccumulator::SingleTouchMotionAccumulator() {
+    clearAbsoluteAxes();
+}
+
+void SingleTouchMotionAccumulator::reset(InputDevice* device) {
+    mAbsX = device->getAbsoluteAxisValue(ABS_X);
+    mAbsY = device->getAbsoluteAxisValue(ABS_Y);
+    mAbsPressure = device->getAbsoluteAxisValue(ABS_PRESSURE);
+    mAbsToolWidth = device->getAbsoluteAxisValue(ABS_TOOL_WIDTH);
+    mAbsDistance = device->getAbsoluteAxisValue(ABS_DISTANCE);
+    mAbsTiltX = device->getAbsoluteAxisValue(ABS_TILT_X);
+    mAbsTiltY = device->getAbsoluteAxisValue(ABS_TILT_Y);
+}
+
+void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
+    mAbsX = 0;
+    mAbsY = 0;
+    mAbsPressure = 0;
+    mAbsToolWidth = 0;
+    mAbsDistance = 0;
+    mAbsTiltX = 0;
+    mAbsTiltY = 0;
+}
+
+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;
+        }
+    }
+}
+
+// --- MultiTouchMotionAccumulator ---
+
+MultiTouchMotionAccumulator::MultiTouchMotionAccumulator()
+      : mCurrentSlot(-1),
+        mSlots(nullptr),
+        mSlotCount(0),
+        mUsingSlotsProtocol(false),
+        mHaveStylus(false) {}
+
+MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() {
+    delete[] mSlots;
+}
+
+void MultiTouchMotionAccumulator::configure(InputDevice* device, size_t slotCount,
+                                            bool usingSlotsProtocol) {
+    mSlotCount = slotCount;
+    mUsingSlotsProtocol = usingSlotsProtocol;
+    mHaveStylus = device->hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
+
+    delete[] mSlots;
+    mSlots = new Slot[slotCount];
+}
+
+void MultiTouchMotionAccumulator::reset(InputDevice* device) {
+    // Unfortunately there is no way to read the initial contents of the slots.
+    // So when we reset the accumulator, we must assume they are all zeroes.
+    if (mUsingSlotsProtocol) {
+        // Query the driver for the current slot index and use it as the initial slot
+        // before we start reading events from the device.  It is possible that the
+        // current slot index will not be the same as it was when the first event was
+        // written into the evdev buffer, which means the input mapper could start
+        // out of sync with the initial state of the events in the evdev buffer.
+        // In the extremely unlikely case that this happens, the data from
+        // two slots will be confused until the next ABS_MT_SLOT event is received.
+        // This can cause the touch point to "jump", but at least there will be
+        // no stuck touches.
+        int32_t initialSlot;
+        status_t status = device->getEventHub()->getAbsoluteAxisValue(device->getId(), ABS_MT_SLOT,
+                                                                      &initialSlot);
+        if (status) {
+            ALOGD("Could not retrieve current multitouch slot index.  status=%d", status);
+            initialSlot = -1;
+        }
+        clearSlots(initialSlot);
+    } else {
+        clearSlots(-1);
+    }
+}
+
+void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
+    if (mSlots) {
+        for (size_t i = 0; i < mSlotCount; i++) {
+            mSlots[i].clear();
+        }
+    }
+    mCurrentSlot = initialSlot;
+}
+
+void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_ABS) {
+        bool newSlot = false;
+        if (mUsingSlotsProtocol) {
+            if (rawEvent->code == ABS_MT_SLOT) {
+                mCurrentSlot = rawEvent->value;
+                newSlot = true;
+            }
+        } else if (mCurrentSlot < 0) {
+            mCurrentSlot = 0;
+        }
+
+        if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
+#if DEBUG_POINTERS
+            if (newSlot) {
+                ALOGW("MultiTouch device emitted invalid slot index %d but it "
+                      "should be between 0 and %zd; ignoring this slot.",
+                      mCurrentSlot, mSlotCount - 1);
+            }
+#endif
+        } else {
+            Slot* slot = &mSlots[mCurrentSlot];
+
+            switch (rawEvent->code) {
+                case ABS_MT_POSITION_X:
+                    slot->mInUse = true;
+                    slot->mAbsMTPositionX = rawEvent->value;
+                    break;
+                case ABS_MT_POSITION_Y:
+                    slot->mInUse = true;
+                    slot->mAbsMTPositionY = rawEvent->value;
+                    break;
+                case ABS_MT_TOUCH_MAJOR:
+                    slot->mInUse = true;
+                    slot->mAbsMTTouchMajor = rawEvent->value;
+                    break;
+                case ABS_MT_TOUCH_MINOR:
+                    slot->mInUse = true;
+                    slot->mAbsMTTouchMinor = rawEvent->value;
+                    slot->mHaveAbsMTTouchMinor = true;
+                    break;
+                case ABS_MT_WIDTH_MAJOR:
+                    slot->mInUse = true;
+                    slot->mAbsMTWidthMajor = rawEvent->value;
+                    break;
+                case ABS_MT_WIDTH_MINOR:
+                    slot->mInUse = true;
+                    slot->mAbsMTWidthMinor = rawEvent->value;
+                    slot->mHaveAbsMTWidthMinor = true;
+                    break;
+                case ABS_MT_ORIENTATION:
+                    slot->mInUse = true;
+                    slot->mAbsMTOrientation = rawEvent->value;
+                    break;
+                case ABS_MT_TRACKING_ID:
+                    if (mUsingSlotsProtocol && rawEvent->value < 0) {
+                        // The slot is no longer in use but it retains its previous contents,
+                        // which may be reused for subsequent touches.
+                        slot->mInUse = false;
+                    } else {
+                        slot->mInUse = true;
+                        slot->mAbsMTTrackingId = rawEvent->value;
+                    }
+                    break;
+                case ABS_MT_PRESSURE:
+                    slot->mInUse = true;
+                    slot->mAbsMTPressure = rawEvent->value;
+                    break;
+                case ABS_MT_DISTANCE:
+                    slot->mInUse = true;
+                    slot->mAbsMTDistance = rawEvent->value;
+                    break;
+                case ABS_MT_TOOL_TYPE:
+                    slot->mInUse = true;
+                    slot->mAbsMTToolType = rawEvent->value;
+                    slot->mHaveAbsMTToolType = true;
+                    break;
+            }
+        }
+    } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+        // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+        mCurrentSlot += 1;
+    }
+}
+
+void MultiTouchMotionAccumulator::finishSync() {
+    if (!mUsingSlotsProtocol) {
+        clearSlots(-1);
+    }
+}
+
+bool MultiTouchMotionAccumulator::hasStylus() const {
+    return mHaveStylus;
+}
+
+// --- MultiTouchMotionAccumulator::Slot ---
+
+MultiTouchMotionAccumulator::Slot::Slot() {
+    clear();
+}
+
+void MultiTouchMotionAccumulator::Slot::clear() {
+    mInUse = false;
+    mHaveAbsMTTouchMinor = false;
+    mHaveAbsMTWidthMinor = false;
+    mHaveAbsMTToolType = false;
+    mAbsMTPositionX = 0;
+    mAbsMTPositionY = 0;
+    mAbsMTTouchMajor = 0;
+    mAbsMTTouchMinor = 0;
+    mAbsMTWidthMajor = 0;
+    mAbsMTWidthMinor = 0;
+    mAbsMTOrientation = 0;
+    mAbsMTTrackingId = -1;
+    mAbsMTPressure = 0;
+    mAbsMTDistance = 0;
+    mAbsMTToolType = 0;
+}
+
+int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+    if (mHaveAbsMTToolType) {
+        switch (mAbsMTToolType) {
+            case MT_TOOL_FINGER:
+                return AMOTION_EVENT_TOOL_TYPE_FINGER;
+            case MT_TOOL_PEN:
+                return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        }
+    }
+    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+// --- InputMapper ---
+
+InputMapper::InputMapper(InputDevice* device) : mDevice(device), mContext(device->getContext()) {}
+
+InputMapper::~InputMapper() {}
+
+void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    info->addSource(getSources());
+}
+
+void InputMapper::dump(std::string& dump) {}
+
+void InputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                            uint32_t changes) {}
+
+void InputMapper::reset(nsecs_t when) {}
+
+void InputMapper::timeoutExpired(nsecs_t when) {}
+
+int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+    return AKEY_STATE_UNKNOWN;
+}
+
+bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+                                        const int32_t* keyCodes, uint8_t* outFlags) {
+    return false;
+}
+
+void InputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+                          int32_t token) {}
+
+void InputMapper::cancelVibrate(int32_t token) {}
+
+void InputMapper::cancelTouch(nsecs_t when) {}
+
+int32_t InputMapper::getMetaState() {
+    return 0;
+}
+
+void InputMapper::updateMetaState(int32_t keyCode) {}
+
+void InputMapper::updateExternalStylusState(const StylusState& state) {}
+
+void InputMapper::fadePointer() {}
+
+status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) {
+    return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo);
+}
+
+void InputMapper::bumpGeneration() {
+    mDevice->bumpGeneration();
+}
+
+void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
+                                          const char* name) {
+    if (axis.valid) {
+        dump += StringPrintf(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n", name,
+                             axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution);
+    } else {
+        dump += StringPrintf(INDENT4 "%s: unknown range\n", name);
+    }
+}
+
+void InputMapper::dumpStylusState(std::string& dump, const StylusState& state) {
+    dump += StringPrintf(INDENT4 "When: %" PRId64 "\n", state.when);
+    dump += StringPrintf(INDENT4 "Pressure: %f\n", state.pressure);
+    dump += StringPrintf(INDENT4 "Button State: 0x%08x\n", state.buttons);
+    dump += StringPrintf(INDENT4 "Tool Type: %" PRId32 "\n", state.toolType);
+}
+
+// --- SwitchInputMapper ---
+
+SwitchInputMapper::SwitchInputMapper(InputDevice* device)
+      : InputMapper(device), mSwitchValues(0), mUpdatedSwitchMask(0) {}
+
+SwitchInputMapper::~SwitchInputMapper() {}
+
+uint32_t SwitchInputMapper::getSources() {
+    return AINPUT_SOURCE_SWITCH;
+}
+
+void SwitchInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+        case EV_SW:
+            processSwitch(rawEvent->code, rawEvent->value);
+            break;
+
+        case EV_SYN:
+            if (rawEvent->code == SYN_REPORT) {
+                sync(rawEvent->when);
+            }
+    }
+}
+
+void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) {
+    if (switchCode >= 0 && switchCode < 32) {
+        if (switchValue) {
+            mSwitchValues |= 1 << switchCode;
+        } else {
+            mSwitchValues &= ~(1 << switchCode);
+        }
+        mUpdatedSwitchMask |= 1 << switchCode;
+    }
+}
+
+void SwitchInputMapper::sync(nsecs_t when) {
+    if (mUpdatedSwitchMask) {
+        uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask;
+        NotifySwitchArgs args(mContext->getNextSequenceNum(), when, 0, updatedSwitchValues,
+                              mUpdatedSwitchMask);
+        getListener()->notifySwitch(&args);
+
+        mUpdatedSwitchMask = 0;
+    }
+}
+
+int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+    return getEventHub()->getSwitchState(getDeviceId(), switchCode);
+}
+
+void SwitchInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "Switch Input Mapper:\n";
+    dump += StringPrintf(INDENT3 "SwitchValues: %x\n", mSwitchValues);
+}
+
+// --- VibratorInputMapper ---
+
+VibratorInputMapper::VibratorInputMapper(InputDevice* device)
+      : InputMapper(device), mVibrating(false) {}
+
+VibratorInputMapper::~VibratorInputMapper() {}
+
+uint32_t VibratorInputMapper::getSources() {
+    return 0;
+}
+
+void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    info->setVibrator(true);
+}
+
+void VibratorInputMapper::process(const RawEvent* rawEvent) {
+    // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
+}
+
+void VibratorInputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+                                  int32_t token) {
+#if DEBUG_VIBRATOR
+    std::string patternStr;
+    for (size_t i = 0; i < patternSize; i++) {
+        if (i != 0) {
+            patternStr += ", ";
+        }
+        patternStr += StringPrintf("%" PRId64, pattern[i]);
+    }
+    ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(),
+          patternStr.c_str(), repeat, token);
+#endif
+
+    mVibrating = true;
+    memcpy(mPattern, pattern, patternSize * sizeof(nsecs_t));
+    mPatternSize = patternSize;
+    mRepeat = repeat;
+    mToken = token;
+    mIndex = -1;
+
+    nextStep();
+}
+
+void VibratorInputMapper::cancelVibrate(int32_t token) {
+#if DEBUG_VIBRATOR
+    ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
+#endif
+
+    if (mVibrating && mToken == token) {
+        stopVibrating();
+    }
+}
+
+void VibratorInputMapper::timeoutExpired(nsecs_t when) {
+    if (mVibrating) {
+        if (when >= mNextStepTime) {
+            nextStep();
+        } else {
+            getContext()->requestTimeoutAtTime(mNextStepTime);
+        }
+    }
+}
+
+void VibratorInputMapper::nextStep() {
+    mIndex += 1;
+    if (size_t(mIndex) >= mPatternSize) {
+        if (mRepeat < 0) {
+            // We are done.
+            stopVibrating();
+            return;
+        }
+        mIndex = mRepeat;
+    }
+
+    bool vibratorOn = mIndex & 1;
+    nsecs_t duration = mPattern[mIndex];
+    if (vibratorOn) {
+#if DEBUG_VIBRATOR
+        ALOGD("nextStep: sending vibrate deviceId=%d, duration=%" PRId64, getDeviceId(), duration);
+#endif
+        getEventHub()->vibrate(getDeviceId(), duration);
+    } else {
+#if DEBUG_VIBRATOR
+        ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+        getEventHub()->cancelVibrate(getDeviceId());
+    }
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    mNextStepTime = now + duration;
+    getContext()->requestTimeoutAtTime(mNextStepTime);
+#if DEBUG_VIBRATOR
+    ALOGD("nextStep: scheduled timeout in %0.3fms", duration * 0.000001f);
+#endif
+}
+
+void VibratorInputMapper::stopVibrating() {
+    mVibrating = false;
+#if DEBUG_VIBRATOR
+    ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+    getEventHub()->cancelVibrate(getDeviceId());
+}
+
+void VibratorInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "Vibrator Input Mapper:\n";
+    dump += StringPrintf(INDENT3 "Vibrating: %s\n", toString(mVibrating));
+}
+
+// --- KeyboardInputMapper ---
+
+KeyboardInputMapper::KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType)
+      : InputMapper(device), mSource(source), mKeyboardType(keyboardType) {}
+
+KeyboardInputMapper::~KeyboardInputMapper() {}
+
+uint32_t KeyboardInputMapper::getSources() {
+    return mSource;
+}
+
+int32_t KeyboardInputMapper::getOrientation() {
+    if (mViewport) {
+        return mViewport->orientation;
+    }
+    return DISPLAY_ORIENTATION_0;
+}
+
+int32_t KeyboardInputMapper::getDisplayId() {
+    if (mViewport) {
+        return mViewport->displayId;
+    }
+    return ADISPLAY_ID_NONE;
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    info->setKeyboardType(mKeyboardType);
+    info->setKeyCharacterMap(getEventHub()->getKeyCharacterMap(getDeviceId()));
+}
+
+void KeyboardInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "Keyboard Input Mapper:\n";
+    dumpParameters(dump);
+    dump += StringPrintf(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+    dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation());
+    dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
+    dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState);
+    dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime);
+}
+
+std::optional<DisplayViewport> KeyboardInputMapper::findViewport(
+        nsecs_t when, const InputReaderConfiguration* config) {
+    const std::optional<uint8_t> displayPort = mDevice->getAssociatedDisplayPort();
+    if (displayPort) {
+        // Find the viewport that contains the same port
+        return mDevice->getAssociatedViewport();
+    }
+
+    // No associated display defined, try to find default display if orientationAware.
+    if (mParameters.orientationAware) {
+        return config->getDisplayViewportByType(ViewportType::VIEWPORT_INTERNAL);
+    }
+
+    return std::nullopt;
+}
+
+void KeyboardInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                                    uint32_t changes) {
+    InputMapper::configure(when, config, changes);
+
+    if (!changes) { // first time only
+        // Configure basic parameters.
+        configureParameters();
+    }
+
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+        mViewport = findViewport(when, config);
+    }
+}
+
+static void mapStemKey(int32_t keyCode, const PropertyMap& config, char const* property) {
+    int32_t mapped = 0;
+    if (config.tryGetProperty(String8(property), mapped) && mapped > 0) {
+        for (size_t i = 0; i < stemKeyRotationMapSize; i++) {
+            if (stemKeyRotationMap[i][0] == keyCode) {
+                stemKeyRotationMap[i][1] = mapped;
+                return;
+            }
+        }
+    }
+}
+
+void KeyboardInputMapper::configureParameters() {
+    mParameters.orientationAware = false;
+    const PropertyMap& config = getDevice()->getConfiguration();
+    config.tryGetProperty(String8("keyboard.orientationAware"), mParameters.orientationAware);
+
+    if (mParameters.orientationAware) {
+        mapStemKey(AKEYCODE_STEM_PRIMARY, config, "keyboard.rotated.stem_primary");
+        mapStemKey(AKEYCODE_STEM_1, config, "keyboard.rotated.stem_1");
+        mapStemKey(AKEYCODE_STEM_2, config, "keyboard.rotated.stem_2");
+        mapStemKey(AKEYCODE_STEM_3, config, "keyboard.rotated.stem_3");
+    }
+
+    mParameters.handlesKeyRepeat = false;
+    config.tryGetProperty(String8("keyboard.handlesKeyRepeat"), mParameters.handlesKeyRepeat);
+}
+
+void KeyboardInputMapper::dumpParameters(std::string& dump) {
+    dump += INDENT3 "Parameters:\n";
+    dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
+    dump += StringPrintf(INDENT4 "HandlesKeyRepeat: %s\n", toString(mParameters.handlesKeyRepeat));
+}
+
+void KeyboardInputMapper::reset(nsecs_t when) {
+    mMetaState = AMETA_NONE;
+    mDownTime = 0;
+    mKeyDowns.clear();
+    mCurrentHidUsage = 0;
+
+    resetLedState();
+
+    InputMapper::reset(when);
+}
+
+void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+        case EV_KEY: {
+            int32_t scanCode = rawEvent->code;
+            int32_t usageCode = mCurrentHidUsage;
+            mCurrentHidUsage = 0;
+
+            if (isKeyboardOrGamepadKey(scanCode)) {
+                processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
+            }
+            break;
+        }
+        case EV_MSC: {
+            if (rawEvent->code == MSC_SCAN) {
+                mCurrentHidUsage = rawEvent->value;
+            }
+            break;
+        }
+        case EV_SYN: {
+            if (rawEvent->code == SYN_REPORT) {
+                mCurrentHidUsage = 0;
+            }
+        }
+    }
+}
+
+bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
+    return scanCode < BTN_MOUSE || scanCode >= KEY_OK ||
+            (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) ||
+            (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
+}
+
+bool KeyboardInputMapper::isMediaKey(int32_t keyCode) {
+    switch (keyCode) {
+        case AKEYCODE_MEDIA_PLAY:
+        case AKEYCODE_MEDIA_PAUSE:
+        case AKEYCODE_MEDIA_PLAY_PAUSE:
+        case AKEYCODE_MUTE:
+        case AKEYCODE_HEADSETHOOK:
+        case AKEYCODE_MEDIA_STOP:
+        case AKEYCODE_MEDIA_NEXT:
+        case AKEYCODE_MEDIA_PREVIOUS:
+        case AKEYCODE_MEDIA_REWIND:
+        case AKEYCODE_MEDIA_RECORD:
+        case AKEYCODE_MEDIA_FAST_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_BACKWARD:
+        case AKEYCODE_MEDIA_STEP_FORWARD:
+        case AKEYCODE_MEDIA_STEP_BACKWARD:
+        case AKEYCODE_MEDIA_AUDIO_TRACK:
+        case AKEYCODE_VOLUME_UP:
+        case AKEYCODE_VOLUME_DOWN:
+        case AKEYCODE_VOLUME_MUTE:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN:
+            return true;
+    }
+    return false;
+}
+
+void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode, int32_t usageCode) {
+    int32_t keyCode;
+    int32_t keyMetaState;
+    uint32_t policyFlags;
+
+    if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState, &keyCode,
+                              &keyMetaState, &policyFlags)) {
+        keyCode = AKEYCODE_UNKNOWN;
+        keyMetaState = mMetaState;
+        policyFlags = 0;
+    }
+
+    if (down) {
+        // Rotate key codes according to orientation if needed.
+        if (mParameters.orientationAware) {
+            keyCode = rotateKeyCode(keyCode, getOrientation());
+        }
+
+        // Add key down.
+        ssize_t keyDownIndex = findKeyDown(scanCode);
+        if (keyDownIndex >= 0) {
+            // key repeat, be sure to use same keycode as before in case of rotation
+            keyCode = mKeyDowns[keyDownIndex].keyCode;
+        } else {
+            // key down
+            if ((policyFlags & POLICY_FLAG_VIRTUAL) &&
+                mContext->shouldDropVirtualKey(when, getDevice(), keyCode, scanCode)) {
+                return;
+            }
+            if (policyFlags & POLICY_FLAG_GESTURE) {
+                mDevice->cancelTouch(when);
+            }
+
+            KeyDown keyDown;
+            keyDown.keyCode = keyCode;
+            keyDown.scanCode = scanCode;
+            mKeyDowns.push_back(keyDown);
+        }
+
+        mDownTime = when;
+    } else {
+        // Remove key down.
+        ssize_t keyDownIndex = findKeyDown(scanCode);
+        if (keyDownIndex >= 0) {
+            // key up, be sure to use same keycode as before in case of rotation
+            keyCode = mKeyDowns[keyDownIndex].keyCode;
+            mKeyDowns.erase(mKeyDowns.begin() + (size_t)keyDownIndex);
+        } else {
+            // key was not actually down
+            ALOGI("Dropping key up from device %s because the key was not down.  "
+                  "keyCode=%d, scanCode=%d",
+                  getDeviceName().c_str(), keyCode, scanCode);
+            return;
+        }
+    }
+
+    if (updateMetaStateIfNeeded(keyCode, down)) {
+        // If global meta state changed send it along with the key.
+        // If it has not changed then we'll use what keymap gave us,
+        // since key replacement logic might temporarily reset a few
+        // meta bits for given key.
+        keyMetaState = mMetaState;
+    }
+
+    nsecs_t downTime = mDownTime;
+
+    // Key down on external an keyboard should wake the device.
+    // We don't do this for internal keyboards to prevent them from waking up in your pocket.
+    // For internal keyboards, the key layout file should specify the policy flags for
+    // each wake key individually.
+    // TODO: Use the input device configuration to control this behavior more finely.
+    if (down && getDevice()->isExternal() && !isMediaKey(keyCode)) {
+        policyFlags |= POLICY_FLAG_WAKE;
+    }
+
+    if (mParameters.handlesKeyRepeat) {
+        policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
+    }
+
+    NotifyKeyArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource, getDisplayId(),
+                       policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+                       AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
+    getListener()->notifyKey(&args);
+}
+
+ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
+    size_t n = mKeyDowns.size();
+    for (size_t i = 0; i < n; i++) {
+        if (mKeyDowns[i].scanCode == scanCode) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    return getEventHub()->getKeyCodeState(getDeviceId(), keyCode);
+}
+
+int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+}
+
+bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+                                                const int32_t* keyCodes, uint8_t* outFlags) {
+    return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags);
+}
+
+int32_t KeyboardInputMapper::getMetaState() {
+    return mMetaState;
+}
+
+void KeyboardInputMapper::updateMetaState(int32_t keyCode) {
+    updateMetaStateIfNeeded(keyCode, false);
+}
+
+bool KeyboardInputMapper::updateMetaStateIfNeeded(int32_t keyCode, bool down) {
+    int32_t oldMetaState = mMetaState;
+    int32_t newMetaState = android::updateMetaState(keyCode, down, oldMetaState);
+    bool metaStateChanged = oldMetaState != newMetaState;
+    if (metaStateChanged) {
+        mMetaState = newMetaState;
+        updateLedState(false);
+
+        getContext()->updateGlobalMetaState();
+    }
+
+    return metaStateChanged;
+}
+
+void KeyboardInputMapper::resetLedState() {
+    initializeLedState(mCapsLockLedState, ALED_CAPS_LOCK);
+    initializeLedState(mNumLockLedState, ALED_NUM_LOCK);
+    initializeLedState(mScrollLockLedState, ALED_SCROLL_LOCK);
+
+    updateLedState(true);
+}
+
+void KeyboardInputMapper::initializeLedState(LedState& ledState, int32_t led) {
+    ledState.avail = getEventHub()->hasLed(getDeviceId(), led);
+    ledState.on = false;
+}
+
+void KeyboardInputMapper::updateLedState(bool reset) {
+    updateLedStateForModifier(mCapsLockLedState, ALED_CAPS_LOCK, AMETA_CAPS_LOCK_ON, reset);
+    updateLedStateForModifier(mNumLockLedState, ALED_NUM_LOCK, AMETA_NUM_LOCK_ON, reset);
+    updateLedStateForModifier(mScrollLockLedState, ALED_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, reset);
+}
+
+void KeyboardInputMapper::updateLedStateForModifier(LedState& ledState, int32_t led,
+                                                    int32_t modifier, bool reset) {
+    if (ledState.avail) {
+        bool desiredState = (mMetaState & modifier) != 0;
+        if (reset || ledState.on != desiredState) {
+            getEventHub()->setLedState(getDeviceId(), led, desiredState);
+            ledState.on = desiredState;
+        }
+    }
+}
+
+std::optional<int32_t> KeyboardInputMapper::getAssociatedDisplayId() {
+    if (mViewport) {
+        return std::make_optional(mViewport->displayId);
+    }
+    return std::nullopt;
+}
+
+// --- CursorInputMapper ---
+
+CursorInputMapper::CursorInputMapper(InputDevice* device) : InputMapper(device) {}
+
+CursorInputMapper::~CursorInputMapper() {}
+
+uint32_t CursorInputMapper::getSources() {
+    return mSource;
+}
+
+void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    if (mParameters.mode == Parameters::MODE_POINTER) {
+        float minX, minY, maxX, maxY;
+        if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+            info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
+            info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f);
+        }
+    } else {
+        info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
+        info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
+    }
+    info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+
+    if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+        info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+    }
+    if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+        info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+    }
+}
+
+void CursorInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "Cursor Input Mapper:\n";
+    dumpParameters(dump);
+    dump += StringPrintf(INDENT3 "XScale: %0.3f\n", mXScale);
+    dump += StringPrintf(INDENT3 "YScale: %0.3f\n", mYScale);
+    dump += StringPrintf(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+    dump += StringPrintf(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+    dump += StringPrintf(INDENT3 "HaveVWheel: %s\n",
+                         toString(mCursorScrollAccumulator.haveRelativeVWheel()));
+    dump += StringPrintf(INDENT3 "HaveHWheel: %s\n",
+                         toString(mCursorScrollAccumulator.haveRelativeHWheel()));
+    dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
+    dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
+    dump += StringPrintf(INDENT3 "Orientation: %d\n", mOrientation);
+    dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
+    dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
+    dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime);
+}
+
+void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                                  uint32_t changes) {
+    InputMapper::configure(when, config, changes);
+
+    if (!changes) { // first time only
+        mCursorScrollAccumulator.configure(getDevice());
+
+        // Configure basic parameters.
+        configureParameters();
+
+        // Configure device mode.
+        switch (mParameters.mode) {
+            case Parameters::MODE_POINTER_RELATIVE:
+                // Should not happen during first time configuration.
+                ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER");
+                mParameters.mode = Parameters::MODE_POINTER;
+                [[fallthrough]];
+            case Parameters::MODE_POINTER:
+                mSource = AINPUT_SOURCE_MOUSE;
+                mXPrecision = 1.0f;
+                mYPrecision = 1.0f;
+                mXScale = 1.0f;
+                mYScale = 1.0f;
+                mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+                break;
+            case Parameters::MODE_NAVIGATION:
+                mSource = AINPUT_SOURCE_TRACKBALL;
+                mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+                mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+                mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+                mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+                break;
+        }
+
+        mVWheelScale = 1.0f;
+        mHWheelScale = 1.0f;
+    }
+
+    if ((!changes && config->pointerCapture) ||
+        (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)) {
+        if (config->pointerCapture) {
+            if (mParameters.mode == Parameters::MODE_POINTER) {
+                mParameters.mode = Parameters::MODE_POINTER_RELATIVE;
+                mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
+                // Keep PointerController around in order to preserve the pointer position.
+                mPointerController->fade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+            } else {
+                ALOGE("Cannot request pointer capture, device is not in MODE_POINTER");
+            }
+        } else {
+            if (mParameters.mode == Parameters::MODE_POINTER_RELATIVE) {
+                mParameters.mode = Parameters::MODE_POINTER;
+                mSource = AINPUT_SOURCE_MOUSE;
+            } else {
+                ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE");
+            }
+        }
+        bumpGeneration();
+        if (changes) {
+            getDevice()->notifyReset(when);
+        }
+    }
+
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+        mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
+        mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
+        mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
+    }
+
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+        mOrientation = DISPLAY_ORIENTATION_0;
+        if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+            std::optional<DisplayViewport> internalViewport =
+                    config->getDisplayViewportByType(ViewportType::VIEWPORT_INTERNAL);
+            if (internalViewport) {
+                mOrientation = internalViewport->orientation;
+            }
+        }
+
+        // Update the PointerController if viewports changed.
+        if (mParameters.mode == Parameters::MODE_POINTER) {
+            getPolicy()->obtainPointerController(getDeviceId());
+        }
+        bumpGeneration();
+    }
+}
+
+void CursorInputMapper::configureParameters() {
+    mParameters.mode = Parameters::MODE_POINTER;
+    String8 cursorModeString;
+    if (getDevice()->getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) {
+        if (cursorModeString == "navigation") {
+            mParameters.mode = Parameters::MODE_NAVIGATION;
+        } else if (cursorModeString != "pointer" && cursorModeString != "default") {
+            ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
+        }
+    }
+
+    mParameters.orientationAware = false;
+    getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
+                                                   mParameters.orientationAware);
+
+    mParameters.hasAssociatedDisplay = false;
+    if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) {
+        mParameters.hasAssociatedDisplay = true;
+    }
+}
+
+void CursorInputMapper::dumpParameters(std::string& dump) {
+    dump += INDENT3 "Parameters:\n";
+    dump += StringPrintf(INDENT4 "HasAssociatedDisplay: %s\n",
+                         toString(mParameters.hasAssociatedDisplay));
+
+    switch (mParameters.mode) {
+        case Parameters::MODE_POINTER:
+            dump += INDENT4 "Mode: pointer\n";
+            break;
+        case Parameters::MODE_POINTER_RELATIVE:
+            dump += INDENT4 "Mode: relative pointer\n";
+            break;
+        case Parameters::MODE_NAVIGATION:
+            dump += INDENT4 "Mode: navigation\n";
+            break;
+        default:
+            ALOG_ASSERT(false);
+    }
+
+    dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
+}
+
+void CursorInputMapper::reset(nsecs_t when) {
+    mButtonState = 0;
+    mDownTime = 0;
+
+    mPointerVelocityControl.reset();
+    mWheelXVelocityControl.reset();
+    mWheelYVelocityControl.reset();
+
+    mCursorButtonAccumulator.reset(getDevice());
+    mCursorMotionAccumulator.reset(getDevice());
+    mCursorScrollAccumulator.reset(getDevice());
+
+    InputMapper::reset(when);
+}
+
+void CursorInputMapper::process(const RawEvent* rawEvent) {
+    mCursorButtonAccumulator.process(rawEvent);
+    mCursorMotionAccumulator.process(rawEvent);
+    mCursorScrollAccumulator.process(rawEvent);
+
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+        sync(rawEvent->when);
+    }
+}
+
+void CursorInputMapper::sync(nsecs_t when) {
+    int32_t lastButtonState = mButtonState;
+    int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
+    mButtonState = currentButtonState;
+
+    bool wasDown = isPointerDown(lastButtonState);
+    bool down = isPointerDown(currentButtonState);
+    bool downChanged;
+    if (!wasDown && down) {
+        mDownTime = when;
+        downChanged = true;
+    } else if (wasDown && !down) {
+        downChanged = true;
+    } else {
+        downChanged = false;
+    }
+    nsecs_t downTime = mDownTime;
+    bool buttonsChanged = currentButtonState != lastButtonState;
+    int32_t buttonsPressed = currentButtonState & ~lastButtonState;
+    int32_t buttonsReleased = lastButtonState & ~currentButtonState;
+
+    float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
+    float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
+    bool moved = deltaX != 0 || deltaY != 0;
+
+    // Rotate delta according to orientation if needed.
+    if (mParameters.orientationAware && mParameters.hasAssociatedDisplay &&
+        (deltaX != 0.0f || deltaY != 0.0f)) {
+        rotateDelta(mOrientation, &deltaX, &deltaY);
+    }
+
+    // Move the pointer.
+    PointerProperties pointerProperties;
+    pointerProperties.clear();
+    pointerProperties.id = 0;
+    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE;
+
+    PointerCoords pointerCoords;
+    pointerCoords.clear();
+
+    float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
+    float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
+    bool scrolled = vscroll != 0 || hscroll != 0;
+
+    mWheelYVelocityControl.move(when, nullptr, &vscroll);
+    mWheelXVelocityControl.move(when, &hscroll, nullptr);
+
+    mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+    int32_t displayId;
+    float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+    float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+    if (mSource == AINPUT_SOURCE_MOUSE) {
+        if (moved || scrolled || buttonsChanged) {
+            mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+
+            if (moved) {
+                mPointerController->move(deltaX, deltaY);
+            }
+
+            if (buttonsChanged) {
+                mPointerController->setButtonState(currentButtonState);
+            }
+
+            mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+        }
+
+        mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
+        displayId = mPointerController->getDisplayId();
+    } else {
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY);
+        displayId = ADISPLAY_ID_NONE;
+    }
+
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
+
+    // Moving an external trackball or mouse should wake the device.
+    // We don't do this for internal cursor devices to prevent them from waking up
+    // the device in your pocket.
+    // TODO: Use the input device configuration to control this behavior more finely.
+    uint32_t policyFlags = 0;
+    if ((buttonsPressed || moved || scrolled) && getDevice()->isExternal()) {
+        policyFlags |= POLICY_FLAG_WAKE;
+    }
+
+    // Synthesize key down from buttons if needed.
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+                         displayId, policyFlags, lastButtonState, currentButtonState);
+
+    // Send motion event.
+    if (downChanged || moved || scrolled || buttonsChanged) {
+        int32_t metaState = mContext->getGlobalMetaState();
+        int32_t buttonState = lastButtonState;
+        int32_t motionEventAction;
+        if (downChanged) {
+            motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+        } else if (down || (mSource != AINPUT_SOURCE_MOUSE)) {
+            motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+        } else {
+            motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
+        }
+
+        if (buttonsReleased) {
+            BitSet32 released(buttonsReleased);
+            while (!released.isEmpty()) {
+                int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit());
+                buttonState &= ~actionButton;
+                NotifyMotionArgs releaseArgs(mContext->getNextSequenceNum(), when, getDeviceId(),
+                                             mSource, displayId, policyFlags,
+                                             AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
+                                             metaState, buttonState, MotionClassification::NONE,
+                                             AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+                                             &pointerCoords, mXPrecision, mYPrecision,
+                                             xCursorPosition, yCursorPosition, downTime,
+                                             /* videoFrames */ {});
+                getListener()->notifyMotion(&releaseArgs);
+            }
+        }
+
+        NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                              displayId, policyFlags, motionEventAction, 0, 0, metaState,
+                              currentButtonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
+                              mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime,
+                              /* videoFrames */ {});
+        getListener()->notifyMotion(&args);
+
+        if (buttonsPressed) {
+            BitSet32 pressed(buttonsPressed);
+            while (!pressed.isEmpty()) {
+                int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit());
+                buttonState |= actionButton;
+                NotifyMotionArgs pressArgs(mContext->getNextSequenceNum(), when, getDeviceId(),
+                                           mSource, displayId, policyFlags,
+                                           AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0,
+                                           metaState, buttonState, MotionClassification::NONE,
+                                           AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+                                           &pointerCoords, mXPrecision, mYPrecision,
+                                           xCursorPosition, yCursorPosition, downTime,
+                                           /* videoFrames */ {});
+                getListener()->notifyMotion(&pressArgs);
+            }
+        }
+
+        ALOG_ASSERT(buttonState == currentButtonState);
+
+        // Send hover move after UP to tell the application that the mouse is hovering now.
+        if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) {
+            NotifyMotionArgs hoverArgs(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                                       displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+                                       0, metaState, currentButtonState, MotionClassification::NONE,
+                                       AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+                                       &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
+                                       yCursorPosition, downTime, /* videoFrames */ {});
+            getListener()->notifyMotion(&hoverArgs);
+        }
+
+        // Send scroll events.
+        if (scrolled) {
+            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+            NotifyMotionArgs scrollArgs(mContext->getNextSequenceNum(), when, getDeviceId(),
+                                        mSource, displayId, policyFlags,
+                                        AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
+                                        currentButtonState, MotionClassification::NONE,
+                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+                                        &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
+                                        yCursorPosition, downTime, /* videoFrames */ {});
+            getListener()->notifyMotion(&scrollArgs);
+        }
+    }
+
+    // Synthesize key up from buttons if needed.
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+                         displayId, policyFlags, lastButtonState, currentButtonState);
+
+    mCursorMotionAccumulator.finishSync();
+    mCursorScrollAccumulator.finishSync();
+}
+
+int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) {
+        return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+    } else {
+        return AKEY_STATE_UNKNOWN;
+    }
+}
+
+void CursorInputMapper::fadePointer() {
+    if (mPointerController != nullptr) {
+        mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+    }
+}
+
+std::optional<int32_t> CursorInputMapper::getAssociatedDisplayId() {
+    if (mParameters.hasAssociatedDisplay) {
+        if (mParameters.mode == Parameters::MODE_POINTER) {
+            return std::make_optional(mPointerController->getDisplayId());
+        } else {
+            // If the device is orientationAware and not a mouse,
+            // it expects to dispatch events to any display
+            return std::make_optional(ADISPLAY_ID_NONE);
+        }
+    }
+    return std::nullopt;
+}
+
+// --- RotaryEncoderInputMapper ---
+
+RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDevice* device)
+      : InputMapper(device), mOrientation(DISPLAY_ORIENTATION_0) {
+    mSource = AINPUT_SOURCE_ROTARY_ENCODER;
+}
+
+RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {}
+
+uint32_t RotaryEncoderInputMapper::getSources() {
+    return mSource;
+}
+
+void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    if (mRotaryEncoderScrollAccumulator.haveRelativeVWheel()) {
+        float res = 0.0f;
+        if (!mDevice->getConfiguration().tryGetProperty(String8("device.res"), res)) {
+            ALOGW("Rotary Encoder device configuration file didn't specify resolution!\n");
+        }
+        if (!mDevice->getConfiguration().tryGetProperty(String8("device.scalingFactor"),
+                                                        mScalingFactor)) {
+            ALOGW("Rotary Encoder device configuration file didn't specify scaling factor,"
+                  "default to 1.0!\n");
+            mScalingFactor = 1.0f;
+        }
+        info->addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+                             res * mScalingFactor);
+    }
+}
+
+void RotaryEncoderInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "Rotary Encoder Input Mapper:\n";
+    dump += StringPrintf(INDENT3 "HaveWheel: %s\n",
+                         toString(mRotaryEncoderScrollAccumulator.haveRelativeVWheel()));
+}
+
+void RotaryEncoderInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                                         uint32_t changes) {
+    InputMapper::configure(when, config, changes);
+    if (!changes) {
+        mRotaryEncoderScrollAccumulator.configure(getDevice());
+    }
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+        std::optional<DisplayViewport> internalViewport =
+                config->getDisplayViewportByType(ViewportType::VIEWPORT_INTERNAL);
+        if (internalViewport) {
+            mOrientation = internalViewport->orientation;
+        } else {
+            mOrientation = DISPLAY_ORIENTATION_0;
+        }
+    }
+}
+
+void RotaryEncoderInputMapper::reset(nsecs_t when) {
+    mRotaryEncoderScrollAccumulator.reset(getDevice());
+
+    InputMapper::reset(when);
+}
+
+void RotaryEncoderInputMapper::process(const RawEvent* rawEvent) {
+    mRotaryEncoderScrollAccumulator.process(rawEvent);
+
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+        sync(rawEvent->when);
+    }
+}
+
+void RotaryEncoderInputMapper::sync(nsecs_t when) {
+    PointerCoords pointerCoords;
+    pointerCoords.clear();
+
+    PointerProperties pointerProperties;
+    pointerProperties.clear();
+    pointerProperties.id = 0;
+    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+    float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
+    bool scrolled = scroll != 0;
+
+    // This is not a pointer, so it's not associated with a display.
+    int32_t displayId = ADISPLAY_ID_NONE;
+
+    // Moving the rotary encoder should wake the device (if specified).
+    uint32_t policyFlags = 0;
+    if (scrolled && getDevice()->isExternal()) {
+        policyFlags |= POLICY_FLAG_WAKE;
+    }
+
+    if (mOrientation == DISPLAY_ORIENTATION_180) {
+        scroll = -scroll;
+    }
+
+    // Send motion event.
+    if (scrolled) {
+        int32_t metaState = mContext->getGlobalMetaState();
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor);
+
+        NotifyMotionArgs scrollArgs(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                                    displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
+                                    metaState, /* buttonState */ 0, MotionClassification::NONE,
+                                    AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+                                    &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                    AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
+        getListener()->notifyMotion(&scrollArgs);
+    }
+
+    mRotaryEncoderScrollAccumulator.finishSync();
+}
+
+// --- TouchInputMapper ---
+
+TouchInputMapper::TouchInputMapper(InputDevice* device)
+      : InputMapper(device),
+        mSource(0),
+        mDeviceMode(DEVICE_MODE_DISABLED),
+        mSurfaceWidth(-1),
+        mSurfaceHeight(-1),
+        mSurfaceLeft(0),
+        mSurfaceTop(0),
+        mPhysicalWidth(-1),
+        mPhysicalHeight(-1),
+        mPhysicalLeft(0),
+        mPhysicalTop(0),
+        mSurfaceOrientation(DISPLAY_ORIENTATION_0) {}
+
+TouchInputMapper::~TouchInputMapper() {}
+
+uint32_t TouchInputMapper::getSources() {
+    return mSource;
+}
+
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    if (mDeviceMode != DEVICE_MODE_DISABLED) {
+        info->addMotionRange(mOrientedRanges.x);
+        info->addMotionRange(mOrientedRanges.y);
+        info->addMotionRange(mOrientedRanges.pressure);
+
+        if (mOrientedRanges.haveSize) {
+            info->addMotionRange(mOrientedRanges.size);
+        }
+
+        if (mOrientedRanges.haveTouchSize) {
+            info->addMotionRange(mOrientedRanges.touchMajor);
+            info->addMotionRange(mOrientedRanges.touchMinor);
+        }
+
+        if (mOrientedRanges.haveToolSize) {
+            info->addMotionRange(mOrientedRanges.toolMajor);
+            info->addMotionRange(mOrientedRanges.toolMinor);
+        }
+
+        if (mOrientedRanges.haveOrientation) {
+            info->addMotionRange(mOrientedRanges.orientation);
+        }
+
+        if (mOrientedRanges.haveDistance) {
+            info->addMotionRange(mOrientedRanges.distance);
+        }
+
+        if (mOrientedRanges.haveTilt) {
+            info->addMotionRange(mOrientedRanges.tilt);
+        }
+
+        if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+            info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+                                 0.0f);
+        }
+        if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+            info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+                                 0.0f);
+        }
+        if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+            const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
+            const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
+            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat,
+                                 x.fuzz, x.resolution);
+            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat,
+                                 y.fuzz, y.resolution);
+            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat,
+                                 x.fuzz, x.resolution);
+            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat,
+                                 y.fuzz, y.resolution);
+        }
+        info->setButtonUnderPad(mParameters.hasButtonUnderPad);
+    }
+}
+
+void TouchInputMapper::dump(std::string& dump) {
+    dump += StringPrintf(INDENT2 "Touch Input Mapper (mode - %s):\n", modeToString(mDeviceMode));
+    dumpParameters(dump);
+    dumpVirtualKeys(dump);
+    dumpRawPointerAxes(dump);
+    dumpCalibration(dump);
+    dumpAffineTransformation(dump);
+    dumpSurface(dump);
+
+    dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n");
+    dump += StringPrintf(INDENT4 "XTranslate: %0.3f\n", mXTranslate);
+    dump += StringPrintf(INDENT4 "YTranslate: %0.3f\n", mYTranslate);
+    dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale);
+    dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale);
+    dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
+    dump += StringPrintf(INDENT4 "YPrecision: %0.3f\n", mYPrecision);
+    dump += StringPrintf(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale);
+    dump += StringPrintf(INDENT4 "PressureScale: %0.3f\n", mPressureScale);
+    dump += StringPrintf(INDENT4 "SizeScale: %0.3f\n", mSizeScale);
+    dump += StringPrintf(INDENT4 "OrientationScale: %0.3f\n", mOrientationScale);
+    dump += StringPrintf(INDENT4 "DistanceScale: %0.3f\n", mDistanceScale);
+    dump += StringPrintf(INDENT4 "HaveTilt: %s\n", toString(mHaveTilt));
+    dump += StringPrintf(INDENT4 "TiltXCenter: %0.3f\n", mTiltXCenter);
+    dump += StringPrintf(INDENT4 "TiltXScale: %0.3f\n", mTiltXScale);
+    dump += StringPrintf(INDENT4 "TiltYCenter: %0.3f\n", mTiltYCenter);
+    dump += StringPrintf(INDENT4 "TiltYScale: %0.3f\n", mTiltYScale);
+
+    dump += StringPrintf(INDENT3 "Last Raw Button State: 0x%08x\n", mLastRawState.buttonState);
+    dump += StringPrintf(INDENT3 "Last Raw Touch: pointerCount=%d\n",
+                         mLastRawState.rawPointerData.pointerCount);
+    for (uint32_t i = 0; i < mLastRawState.rawPointerData.pointerCount; i++) {
+        const RawPointerData::Pointer& pointer = mLastRawState.rawPointerData.pointers[i];
+        dump += StringPrintf(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
+                                     "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
+                                     "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, "
+                                     "toolType=%d, isHovering=%s\n",
+                             i, pointer.id, pointer.x, pointer.y, pointer.pressure,
+                             pointer.touchMajor, pointer.touchMinor, pointer.toolMajor,
+                             pointer.toolMinor, pointer.orientation, pointer.tiltX, pointer.tiltY,
+                             pointer.distance, pointer.toolType, toString(pointer.isHovering));
+    }
+
+    dump += StringPrintf(INDENT3 "Last Cooked Button State: 0x%08x\n",
+                         mLastCookedState.buttonState);
+    dump += StringPrintf(INDENT3 "Last Cooked Touch: pointerCount=%d\n",
+                         mLastCookedState.cookedPointerData.pointerCount);
+    for (uint32_t i = 0; i < mLastCookedState.cookedPointerData.pointerCount; i++) {
+        const PointerProperties& pointerProperties =
+                mLastCookedState.cookedPointerData.pointerProperties[i];
+        const PointerCoords& pointerCoords = mLastCookedState.cookedPointerData.pointerCoords[i];
+        dump += StringPrintf(INDENT4 "[%d]: id=%d, x=%0.3f, y=%0.3f, pressure=%0.3f, "
+                                     "touchMajor=%0.3f, touchMinor=%0.3f, toolMajor=%0.3f, "
+                                     "toolMinor=%0.3f, "
+                                     "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, "
+                                     "toolType=%d, isHovering=%s\n",
+                             i, pointerProperties.id, pointerCoords.getX(), pointerCoords.getY(),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT),
+                             pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE),
+                             pointerProperties.toolType,
+                             toString(mLastCookedState.cookedPointerData.isHovering(i)));
+    }
+
+    dump += INDENT3 "Stylus Fusion:\n";
+    dump += StringPrintf(INDENT4 "ExternalStylusConnected: %s\n",
+                         toString(mExternalStylusConnected));
+    dump += StringPrintf(INDENT4 "External Stylus ID: %" PRId64 "\n", mExternalStylusId);
+    dump += StringPrintf(INDENT4 "External Stylus Data Timeout: %" PRId64 "\n",
+                         mExternalStylusFusionTimeout);
+    dump += INDENT3 "External Stylus State:\n";
+    dumpStylusState(dump, mExternalStylusState);
+
+    if (mDeviceMode == DEVICE_MODE_POINTER) {
+        dump += StringPrintf(INDENT3 "Pointer Gesture Detector:\n");
+        dump += StringPrintf(INDENT4 "XMovementScale: %0.3f\n", mPointerXMovementScale);
+        dump += StringPrintf(INDENT4 "YMovementScale: %0.3f\n", mPointerYMovementScale);
+        dump += StringPrintf(INDENT4 "XZoomScale: %0.3f\n", mPointerXZoomScale);
+        dump += StringPrintf(INDENT4 "YZoomScale: %0.3f\n", mPointerYZoomScale);
+        dump += StringPrintf(INDENT4 "MaxSwipeWidth: %f\n", mPointerGestureMaxSwipeWidth);
+    }
+}
+
+const char* TouchInputMapper::modeToString(DeviceMode deviceMode) {
+    switch (deviceMode) {
+        case DEVICE_MODE_DISABLED:
+            return "disabled";
+        case DEVICE_MODE_DIRECT:
+            return "direct";
+        case DEVICE_MODE_UNSCALED:
+            return "unscaled";
+        case DEVICE_MODE_NAVIGATION:
+            return "navigation";
+        case DEVICE_MODE_POINTER:
+            return "pointer";
+    }
+    return "unknown";
+}
+
+void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                                 uint32_t changes) {
+    InputMapper::configure(when, config, changes);
+
+    mConfig = *config;
+
+    if (!changes) { // first time only
+        // Configure basic parameters.
+        configureParameters();
+
+        // Configure common accumulators.
+        mCursorScrollAccumulator.configure(getDevice());
+        mTouchButtonAccumulator.configure(getDevice());
+
+        // Configure absolute axis information.
+        configureRawPointerAxes();
+
+        // Prepare input device calibration.
+        parseCalibration();
+        resolveCalibration();
+    }
+
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION)) {
+        // Update location calibration to reflect current settings
+        updateAffineTransformation();
+    }
+
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+        // Update pointer speed.
+        mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);
+        mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
+        mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
+    }
+
+    bool resetNeeded = false;
+    if (!changes ||
+        (changes &
+         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
+          InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
+          InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
+          InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
+        // Configure device sources, surface dimensions, orientation and
+        // scaling factors.
+        configureSurface(when, &resetNeeded);
+    }
+
+    if (changes && resetNeeded) {
+        // Send reset, unless this is the first time the device has been configured,
+        // in which case the reader will call reset itself after all mappers are ready.
+        getDevice()->notifyReset(when);
+    }
+}
+
+void TouchInputMapper::resolveExternalStylusPresence() {
+    std::vector<InputDeviceInfo> devices;
+    mContext->getExternalStylusDevices(devices);
+    mExternalStylusConnected = !devices.empty();
+
+    if (!mExternalStylusConnected) {
+        resetExternalStylus();
+    }
+}
+
+void TouchInputMapper::configureParameters() {
+    // Use the pointer presentation mode for devices that do not support distinct
+    // multitouch.  The spot-based presentation relies on being able to accurately
+    // locate two or more fingers on the touch pad.
+    mParameters.gestureMode = getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_SEMI_MT)
+            ? Parameters::GESTURE_MODE_SINGLE_TOUCH
+            : Parameters::GESTURE_MODE_MULTI_TOUCH;
+
+    String8 gestureModeString;
+    if (getDevice()->getConfiguration().tryGetProperty(String8("touch.gestureMode"),
+                                                       gestureModeString)) {
+        if (gestureModeString == "single-touch") {
+            mParameters.gestureMode = Parameters::GESTURE_MODE_SINGLE_TOUCH;
+        } else if (gestureModeString == "multi-touch") {
+            mParameters.gestureMode = Parameters::GESTURE_MODE_MULTI_TOUCH;
+        } else if (gestureModeString != "default") {
+            ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
+        }
+    }
+
+    if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) {
+        // The device is a touch screen.
+        mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+    } else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_POINTER)) {
+        // The device is a pointing device like a track pad.
+        mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+    } else if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X) ||
+               getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
+        // The device is a cursor device with a touch pad attached.
+        // By default don't use the touch pad to move the pointer.
+        mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+    } else {
+        // The device is a touch pad of unknown purpose.
+        mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+    }
+
+    mParameters.hasButtonUnderPad =
+            getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_BUTTONPAD);
+
+    String8 deviceTypeString;
+    if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),
+                                                       deviceTypeString)) {
+        if (deviceTypeString == "touchScreen") {
+            mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+        } else if (deviceTypeString == "touchPad") {
+            mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+        } else if (deviceTypeString == "touchNavigation") {
+            mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION;
+        } else if (deviceTypeString == "pointer") {
+            mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+        } else if (deviceTypeString != "default") {
+            ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
+        }
+    }
+
+    mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+    getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
+                                                   mParameters.orientationAware);
+
+    mParameters.hasAssociatedDisplay = false;
+    mParameters.associatedDisplayIsExternal = false;
+    if (mParameters.orientationAware ||
+        mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN ||
+        mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
+        mParameters.hasAssociatedDisplay = true;
+        if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) {
+            mParameters.associatedDisplayIsExternal = getDevice()->isExternal();
+            String8 uniqueDisplayId;
+            getDevice()->getConfiguration().tryGetProperty(String8("touch.displayId"),
+                                                           uniqueDisplayId);
+            mParameters.uniqueDisplayId = uniqueDisplayId.c_str();
+        }
+    }
+    if (getDevice()->getAssociatedDisplayPort()) {
+        mParameters.hasAssociatedDisplay = true;
+    }
+
+    // Initial downs on external touch devices should wake the device.
+    // Normally we don't do this for internal touch screens to prevent them from waking
+    // up in your pocket but you can enable it using the input device configuration.
+    mParameters.wake = getDevice()->isExternal();
+    getDevice()->getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake);
+}
+
+void TouchInputMapper::dumpParameters(std::string& dump) {
+    dump += INDENT3 "Parameters:\n";
+
+    switch (mParameters.gestureMode) {
+        case Parameters::GESTURE_MODE_SINGLE_TOUCH:
+            dump += INDENT4 "GestureMode: single-touch\n";
+            break;
+        case Parameters::GESTURE_MODE_MULTI_TOUCH:
+            dump += INDENT4 "GestureMode: multi-touch\n";
+            break;
+        default:
+            assert(false);
+    }
+
+    switch (mParameters.deviceType) {
+        case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+            dump += INDENT4 "DeviceType: touchScreen\n";
+            break;
+        case Parameters::DEVICE_TYPE_TOUCH_PAD:
+            dump += INDENT4 "DeviceType: touchPad\n";
+            break;
+        case Parameters::DEVICE_TYPE_TOUCH_NAVIGATION:
+            dump += INDENT4 "DeviceType: touchNavigation\n";
+            break;
+        case Parameters::DEVICE_TYPE_POINTER:
+            dump += INDENT4 "DeviceType: pointer\n";
+            break;
+        default:
+            ALOG_ASSERT(false);
+    }
+
+    dump += StringPrintf(INDENT4 "AssociatedDisplay: hasAssociatedDisplay=%s, isExternal=%s, "
+                                 "displayId='%s'\n",
+                         toString(mParameters.hasAssociatedDisplay),
+                         toString(mParameters.associatedDisplayIsExternal),
+                         mParameters.uniqueDisplayId.c_str());
+    dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
+}
+
+void TouchInputMapper::configureRawPointerAxes() {
+    mRawPointerAxes.clear();
+}
+
+void TouchInputMapper::dumpRawPointerAxes(std::string& dump) {
+    dump += INDENT3 "Raw Touch Axes:\n";
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.x, "X");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.y, "Y");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.pressure, "Pressure");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMajor, "TouchMajor");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMinor, "TouchMinor");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMajor, "ToolMajor");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMinor, "ToolMinor");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.orientation, "Orientation");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.distance, "Distance");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltX, "TiltX");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltY, "TiltY");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.trackingId, "TrackingId");
+    dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.slot, "Slot");
+}
+
+bool TouchInputMapper::hasExternalStylus() const {
+    return mExternalStylusConnected;
+}
+
+/**
+ * Determine which DisplayViewport to use.
+ * 1. If display port is specified, return the matching viewport. If matching viewport not
+ * found, then return.
+ * 2. If a device has associated display, get the matching viewport by either unique id or by
+ * the display type (internal or external).
+ * 3. Otherwise, use a non-display viewport.
+ */
+std::optional<DisplayViewport> TouchInputMapper::findViewport() {
+    if (mParameters.hasAssociatedDisplay) {
+        const std::optional<uint8_t> displayPort = mDevice->getAssociatedDisplayPort();
+        if (displayPort) {
+            // Find the viewport that contains the same port
+            return mDevice->getAssociatedViewport();
+        }
+
+        // Check if uniqueDisplayId is specified in idc file.
+        if (!mParameters.uniqueDisplayId.empty()) {
+            return mConfig.getDisplayViewportByUniqueId(mParameters.uniqueDisplayId);
+        }
+
+        ViewportType viewportTypeToUse;
+        if (mParameters.associatedDisplayIsExternal) {
+            viewportTypeToUse = ViewportType::VIEWPORT_EXTERNAL;
+        } else {
+            viewportTypeToUse = ViewportType::VIEWPORT_INTERNAL;
+        }
+
+        std::optional<DisplayViewport> viewport =
+                mConfig.getDisplayViewportByType(viewportTypeToUse);
+        if (!viewport && viewportTypeToUse == ViewportType::VIEWPORT_EXTERNAL) {
+            ALOGW("Input device %s should be associated with external display, "
+                  "fallback to internal one for the external viewport is not found.",
+                  getDeviceName().c_str());
+            viewport = mConfig.getDisplayViewportByType(ViewportType::VIEWPORT_INTERNAL);
+        }
+
+        return viewport;
+    }
+
+    // No associated display, return a non-display viewport.
+    DisplayViewport newViewport;
+    // Raw width and height in the natural orientation.
+    int32_t rawWidth = mRawPointerAxes.getRawWidth();
+    int32_t rawHeight = mRawPointerAxes.getRawHeight();
+    newViewport.setNonDisplayViewport(rawWidth, rawHeight);
+    return std::make_optional(newViewport);
+}
+
+void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
+    int32_t oldDeviceMode = mDeviceMode;
+
+    resolveExternalStylusPresence();
+
+    // Determine device mode.
+    if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER &&
+        mConfig.pointerGesturesEnabled) {
+        mSource = AINPUT_SOURCE_MOUSE;
+        mDeviceMode = DEVICE_MODE_POINTER;
+        if (hasStylus()) {
+            mSource |= AINPUT_SOURCE_STYLUS;
+        }
+    } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN &&
+               mParameters.hasAssociatedDisplay) {
+        mSource = AINPUT_SOURCE_TOUCHSCREEN;
+        mDeviceMode = DEVICE_MODE_DIRECT;
+        if (hasStylus()) {
+            mSource |= AINPUT_SOURCE_STYLUS;
+        }
+        if (hasExternalStylus()) {
+            mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
+        }
+    } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) {
+        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+        mDeviceMode = DEVICE_MODE_NAVIGATION;
+    } else {
+        mSource = AINPUT_SOURCE_TOUCHPAD;
+        mDeviceMode = DEVICE_MODE_UNSCALED;
+    }
+
+    // Ensure we have valid X and Y axes.
+    if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
+        ALOGW("Touch device '%s' did not report support for X or Y axis!  "
+              "The device will be inoperable.",
+              getDeviceName().c_str());
+        mDeviceMode = DEVICE_MODE_DISABLED;
+        return;
+    }
+
+    // Get associated display dimensions.
+    std::optional<DisplayViewport> newViewport = findViewport();
+    if (!newViewport) {
+        ALOGI("Touch device '%s' could not query the properties of its associated "
+              "display.  The device will be inoperable until the display size "
+              "becomes available.",
+              getDeviceName().c_str());
+        mDeviceMode = DEVICE_MODE_DISABLED;
+        return;
+    }
+
+    // Raw width and height in the natural orientation.
+    int32_t rawWidth = mRawPointerAxes.getRawWidth();
+    int32_t rawHeight = mRawPointerAxes.getRawHeight();
+
+    bool viewportChanged = mViewport != *newViewport;
+    if (viewportChanged) {
+        mViewport = *newViewport;
+
+        if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) {
+            // Convert rotated viewport to natural surface coordinates.
+            int32_t naturalLogicalWidth, naturalLogicalHeight;
+            int32_t naturalPhysicalWidth, naturalPhysicalHeight;
+            int32_t naturalPhysicalLeft, naturalPhysicalTop;
+            int32_t naturalDeviceWidth, naturalDeviceHeight;
+            switch (mViewport.orientation) {
+                case DISPLAY_ORIENTATION_90:
+                    naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+                    naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+                    naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+                    naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+                    naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
+                    naturalPhysicalTop = mViewport.physicalLeft;
+                    naturalDeviceWidth = mViewport.deviceHeight;
+                    naturalDeviceHeight = mViewport.deviceWidth;
+                    break;
+                case DISPLAY_ORIENTATION_180:
+                    naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+                    naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+                    naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+                    naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+                    naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
+                    naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
+                    naturalDeviceWidth = mViewport.deviceWidth;
+                    naturalDeviceHeight = mViewport.deviceHeight;
+                    break;
+                case DISPLAY_ORIENTATION_270:
+                    naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+                    naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+                    naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+                    naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+                    naturalPhysicalLeft = mViewport.physicalTop;
+                    naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
+                    naturalDeviceWidth = mViewport.deviceHeight;
+                    naturalDeviceHeight = mViewport.deviceWidth;
+                    break;
+                case DISPLAY_ORIENTATION_0:
+                default:
+                    naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+                    naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+                    naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+                    naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+                    naturalPhysicalLeft = mViewport.physicalLeft;
+                    naturalPhysicalTop = mViewport.physicalTop;
+                    naturalDeviceWidth = mViewport.deviceWidth;
+                    naturalDeviceHeight = mViewport.deviceHeight;
+                    break;
+            }
+
+            if (naturalPhysicalHeight == 0 || naturalPhysicalWidth == 0) {
+                ALOGE("Viewport is not set properly: %s", mViewport.toString().c_str());
+                naturalPhysicalHeight = naturalPhysicalHeight == 0 ? 1 : naturalPhysicalHeight;
+                naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth;
+            }
+
+            mPhysicalWidth = naturalPhysicalWidth;
+            mPhysicalHeight = naturalPhysicalHeight;
+            mPhysicalLeft = naturalPhysicalLeft;
+            mPhysicalTop = naturalPhysicalTop;
+
+            mSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
+            mSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
+            mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
+            mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
+
+            mSurfaceOrientation =
+                    mParameters.orientationAware ? mViewport.orientation : DISPLAY_ORIENTATION_0;
+        } else {
+            mPhysicalWidth = rawWidth;
+            mPhysicalHeight = rawHeight;
+            mPhysicalLeft = 0;
+            mPhysicalTop = 0;
+
+            mSurfaceWidth = rawWidth;
+            mSurfaceHeight = rawHeight;
+            mSurfaceLeft = 0;
+            mSurfaceTop = 0;
+            mSurfaceOrientation = DISPLAY_ORIENTATION_0;
+        }
+    }
+
+    // If moving between pointer modes, need to reset some state.
+    bool deviceModeChanged = mDeviceMode != oldDeviceMode;
+    if (deviceModeChanged) {
+        mOrientedRanges.clear();
+    }
+
+    // Create or update pointer controller if needed.
+    if (mDeviceMode == DEVICE_MODE_POINTER ||
+        (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) {
+        if (mPointerController == nullptr || viewportChanged) {
+            mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+        }
+    } else {
+        mPointerController.clear();
+    }
+
+    if (viewportChanged || deviceModeChanged) {
+        ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
+              "display id %d",
+              getDeviceId(), getDeviceName().c_str(), mSurfaceWidth, mSurfaceHeight,
+              mSurfaceOrientation, mDeviceMode, mViewport.displayId);
+
+        // Configure X and Y factors.
+        mXScale = float(mSurfaceWidth) / rawWidth;
+        mYScale = float(mSurfaceHeight) / rawHeight;
+        mXTranslate = -mSurfaceLeft;
+        mYTranslate = -mSurfaceTop;
+        mXPrecision = 1.0f / mXScale;
+        mYPrecision = 1.0f / mYScale;
+
+        mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
+        mOrientedRanges.x.source = mSource;
+        mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
+        mOrientedRanges.y.source = mSource;
+
+        configureVirtualKeys();
+
+        // Scale factor for terms that are not oriented in a particular axis.
+        // If the pixels are square then xScale == yScale otherwise we fake it
+        // by choosing an average.
+        mGeometricScale = avg(mXScale, mYScale);
+
+        // Size of diagonal axis.
+        float diagonalSize = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+        // Size factors.
+        if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
+            if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) {
+                mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
+            } else if (mRawPointerAxes.toolMajor.valid && mRawPointerAxes.toolMajor.maxValue != 0) {
+                mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
+            } else {
+                mSizeScale = 0.0f;
+            }
+
+            mOrientedRanges.haveTouchSize = true;
+            mOrientedRanges.haveToolSize = true;
+            mOrientedRanges.haveSize = true;
+
+            mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR;
+            mOrientedRanges.touchMajor.source = mSource;
+            mOrientedRanges.touchMajor.min = 0;
+            mOrientedRanges.touchMajor.max = diagonalSize;
+            mOrientedRanges.touchMajor.flat = 0;
+            mOrientedRanges.touchMajor.fuzz = 0;
+            mOrientedRanges.touchMajor.resolution = 0;
+
+            mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
+            mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
+
+            mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR;
+            mOrientedRanges.toolMajor.source = mSource;
+            mOrientedRanges.toolMajor.min = 0;
+            mOrientedRanges.toolMajor.max = diagonalSize;
+            mOrientedRanges.toolMajor.flat = 0;
+            mOrientedRanges.toolMajor.fuzz = 0;
+            mOrientedRanges.toolMajor.resolution = 0;
+
+            mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
+            mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
+
+            mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE;
+            mOrientedRanges.size.source = mSource;
+            mOrientedRanges.size.min = 0;
+            mOrientedRanges.size.max = 1.0;
+            mOrientedRanges.size.flat = 0;
+            mOrientedRanges.size.fuzz = 0;
+            mOrientedRanges.size.resolution = 0;
+        } else {
+            mSizeScale = 0.0f;
+        }
+
+        // Pressure factors.
+        mPressureScale = 0;
+        float pressureMax = 1.0;
+        if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL ||
+            mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
+            if (mCalibration.havePressureScale) {
+                mPressureScale = mCalibration.pressureScale;
+                pressureMax = mPressureScale * mRawPointerAxes.pressure.maxValue;
+            } else if (mRawPointerAxes.pressure.valid && mRawPointerAxes.pressure.maxValue != 0) {
+                mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
+            }
+        }
+
+        mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE;
+        mOrientedRanges.pressure.source = mSource;
+        mOrientedRanges.pressure.min = 0;
+        mOrientedRanges.pressure.max = pressureMax;
+        mOrientedRanges.pressure.flat = 0;
+        mOrientedRanges.pressure.fuzz = 0;
+        mOrientedRanges.pressure.resolution = 0;
+
+        // Tilt
+        mTiltXCenter = 0;
+        mTiltXScale = 0;
+        mTiltYCenter = 0;
+        mTiltYScale = 0;
+        mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
+        if (mHaveTilt) {
+            mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue, mRawPointerAxes.tiltX.maxValue);
+            mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue, mRawPointerAxes.tiltY.maxValue);
+            mTiltXScale = M_PI / 180;
+            mTiltYScale = M_PI / 180;
+
+            mOrientedRanges.haveTilt = true;
+
+            mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT;
+            mOrientedRanges.tilt.source = mSource;
+            mOrientedRanges.tilt.min = 0;
+            mOrientedRanges.tilt.max = M_PI_2;
+            mOrientedRanges.tilt.flat = 0;
+            mOrientedRanges.tilt.fuzz = 0;
+            mOrientedRanges.tilt.resolution = 0;
+        }
+
+        // Orientation
+        mOrientationScale = 0;
+        if (mHaveTilt) {
+            mOrientedRanges.haveOrientation = true;
+
+            mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+            mOrientedRanges.orientation.source = mSource;
+            mOrientedRanges.orientation.min = -M_PI;
+            mOrientedRanges.orientation.max = M_PI;
+            mOrientedRanges.orientation.flat = 0;
+            mOrientedRanges.orientation.fuzz = 0;
+            mOrientedRanges.orientation.resolution = 0;
+        } else if (mCalibration.orientationCalibration !=
+                   Calibration::ORIENTATION_CALIBRATION_NONE) {
+            if (mCalibration.orientationCalibration ==
+                Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
+                if (mRawPointerAxes.orientation.valid) {
+                    if (mRawPointerAxes.orientation.maxValue > 0) {
+                        mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
+                    } else if (mRawPointerAxes.orientation.minValue < 0) {
+                        mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
+                    } else {
+                        mOrientationScale = 0;
+                    }
+                }
+            }
+
+            mOrientedRanges.haveOrientation = true;
+
+            mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+            mOrientedRanges.orientation.source = mSource;
+            mOrientedRanges.orientation.min = -M_PI_2;
+            mOrientedRanges.orientation.max = M_PI_2;
+            mOrientedRanges.orientation.flat = 0;
+            mOrientedRanges.orientation.fuzz = 0;
+            mOrientedRanges.orientation.resolution = 0;
+        }
+
+        // Distance
+        mDistanceScale = 0;
+        if (mCalibration.distanceCalibration != Calibration::DISTANCE_CALIBRATION_NONE) {
+            if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_SCALED) {
+                if (mCalibration.haveDistanceScale) {
+                    mDistanceScale = mCalibration.distanceScale;
+                } else {
+                    mDistanceScale = 1.0f;
+                }
+            }
+
+            mOrientedRanges.haveDistance = true;
+
+            mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
+            mOrientedRanges.distance.source = mSource;
+            mOrientedRanges.distance.min = mRawPointerAxes.distance.minValue * mDistanceScale;
+            mOrientedRanges.distance.max = mRawPointerAxes.distance.maxValue * mDistanceScale;
+            mOrientedRanges.distance.flat = 0;
+            mOrientedRanges.distance.fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale;
+            mOrientedRanges.distance.resolution = 0;
+        }
+
+        // Compute oriented precision, scales and ranges.
+        // Note that the maximum value reported is an inclusive maximum value so it is one
+        // unit less than the total width or height of surface.
+        switch (mSurfaceOrientation) {
+            case DISPLAY_ORIENTATION_90:
+            case DISPLAY_ORIENTATION_270:
+                mOrientedXPrecision = mYPrecision;
+                mOrientedYPrecision = mXPrecision;
+
+                mOrientedRanges.x.min = mYTranslate;
+                mOrientedRanges.x.max = mSurfaceHeight + mYTranslate - 1;
+                mOrientedRanges.x.flat = 0;
+                mOrientedRanges.x.fuzz = 0;
+                mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
+
+                mOrientedRanges.y.min = mXTranslate;
+                mOrientedRanges.y.max = mSurfaceWidth + mXTranslate - 1;
+                mOrientedRanges.y.flat = 0;
+                mOrientedRanges.y.fuzz = 0;
+                mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
+                break;
+
+            default:
+                mOrientedXPrecision = mXPrecision;
+                mOrientedYPrecision = mYPrecision;
+
+                mOrientedRanges.x.min = mXTranslate;
+                mOrientedRanges.x.max = mSurfaceWidth + mXTranslate - 1;
+                mOrientedRanges.x.flat = 0;
+                mOrientedRanges.x.fuzz = 0;
+                mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
+
+                mOrientedRanges.y.min = mYTranslate;
+                mOrientedRanges.y.max = mSurfaceHeight + mYTranslate - 1;
+                mOrientedRanges.y.flat = 0;
+                mOrientedRanges.y.fuzz = 0;
+                mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
+                break;
+        }
+
+        // Location
+        updateAffineTransformation();
+
+        if (mDeviceMode == DEVICE_MODE_POINTER) {
+            // Compute pointer gesture detection parameters.
+            float rawDiagonal = hypotf(rawWidth, rawHeight);
+            float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+            // Scale movements such that one whole swipe of the touch pad covers a
+            // given area relative to the diagonal size of the display when no acceleration
+            // is applied.
+            // Assume that the touch pad has a square aspect ratio such that movements in
+            // X and Y of the same number of raw units cover the same physical distance.
+            mPointerXMovementScale =
+                    mConfig.pointerGestureMovementSpeedRatio * displayDiagonal / rawDiagonal;
+            mPointerYMovementScale = mPointerXMovementScale;
+
+            // Scale zooms to cover a smaller range of the display than movements do.
+            // This value determines the area around the pointer that is affected by freeform
+            // pointer gestures.
+            mPointerXZoomScale =
+                    mConfig.pointerGestureZoomSpeedRatio * displayDiagonal / rawDiagonal;
+            mPointerYZoomScale = mPointerXZoomScale;
+
+            // Max width between pointers to detect a swipe gesture is more than some fraction
+            // of the diagonal axis of the touch pad.  Touches that are wider than this are
+            // translated into freeform gestures.
+            mPointerGestureMaxSwipeWidth = mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
+
+            // Abort current pointer usages because the state has changed.
+            abortPointerUsage(when, 0 /*policyFlags*/);
+        }
+
+        // Inform the dispatcher about the changes.
+        *outResetNeeded = true;
+        bumpGeneration();
+    }
+}
+
+void TouchInputMapper::dumpSurface(std::string& dump) {
+    dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str());
+    dump += StringPrintf(INDENT3 "SurfaceWidth: %dpx\n", mSurfaceWidth);
+    dump += StringPrintf(INDENT3 "SurfaceHeight: %dpx\n", mSurfaceHeight);
+    dump += StringPrintf(INDENT3 "SurfaceLeft: %d\n", mSurfaceLeft);
+    dump += StringPrintf(INDENT3 "SurfaceTop: %d\n", mSurfaceTop);
+    dump += StringPrintf(INDENT3 "PhysicalWidth: %dpx\n", mPhysicalWidth);
+    dump += StringPrintf(INDENT3 "PhysicalHeight: %dpx\n", mPhysicalHeight);
+    dump += StringPrintf(INDENT3 "PhysicalLeft: %d\n", mPhysicalLeft);
+    dump += StringPrintf(INDENT3 "PhysicalTop: %d\n", mPhysicalTop);
+    dump += StringPrintf(INDENT3 "SurfaceOrientation: %d\n", mSurfaceOrientation);
+}
+
+void TouchInputMapper::configureVirtualKeys() {
+    std::vector<VirtualKeyDefinition> virtualKeyDefinitions;
+    getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions);
+
+    mVirtualKeys.clear();
+
+    if (virtualKeyDefinitions.size() == 0) {
+        return;
+    }
+
+    int32_t touchScreenLeft = mRawPointerAxes.x.minValue;
+    int32_t touchScreenTop = mRawPointerAxes.y.minValue;
+    int32_t touchScreenWidth = mRawPointerAxes.getRawWidth();
+    int32_t touchScreenHeight = mRawPointerAxes.getRawHeight();
+
+    for (const VirtualKeyDefinition& virtualKeyDefinition : virtualKeyDefinitions) {
+        VirtualKey virtualKey;
+
+        virtualKey.scanCode = virtualKeyDefinition.scanCode;
+        int32_t keyCode;
+        int32_t dummyKeyMetaState;
+        uint32_t flags;
+        if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode, 0, 0, &keyCode,
+                                  &dummyKeyMetaState, &flags)) {
+            ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode);
+            continue; // drop the key
+        }
+
+        virtualKey.keyCode = keyCode;
+        virtualKey.flags = flags;
+
+        // convert the key definition's display coordinates into touch coordinates for a hit box
+        int32_t halfWidth = virtualKeyDefinition.width / 2;
+        int32_t halfHeight = virtualKeyDefinition.height / 2;
+
+        virtualKey.hitLeft =
+                (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / mSurfaceWidth +
+                touchScreenLeft;
+        virtualKey.hitRight =
+                (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / mSurfaceWidth +
+                touchScreenLeft;
+        virtualKey.hitTop =
+                (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight / mSurfaceHeight +
+                touchScreenTop;
+        virtualKey.hitBottom =
+                (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight / mSurfaceHeight +
+                touchScreenTop;
+        mVirtualKeys.push_back(virtualKey);
+    }
+}
+
+void TouchInputMapper::dumpVirtualKeys(std::string& dump) {
+    if (!mVirtualKeys.empty()) {
+        dump += INDENT3 "Virtual Keys:\n";
+
+        for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+            const VirtualKey& virtualKey = mVirtualKeys[i];
+            dump += StringPrintf(INDENT4 "%zu: scanCode=%d, keyCode=%d, "
+                                         "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+                                 i, virtualKey.scanCode, virtualKey.keyCode, virtualKey.hitLeft,
+                                 virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
+        }
+    }
+}
+
+void TouchInputMapper::parseCalibration() {
+    const PropertyMap& in = getDevice()->getConfiguration();
+    Calibration& out = mCalibration;
+
+    // Size
+    out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT;
+    String8 sizeCalibrationString;
+    if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) {
+        if (sizeCalibrationString == "none") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+        } else if (sizeCalibrationString == "geometric") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+        } else if (sizeCalibrationString == "diameter") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_DIAMETER;
+        } else if (sizeCalibrationString == "box") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_BOX;
+        } else if (sizeCalibrationString == "area") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_AREA;
+        } else if (sizeCalibrationString != "default") {
+            ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString.string());
+        }
+    }
+
+    out.haveSizeScale = in.tryGetProperty(String8("touch.size.scale"), out.sizeScale);
+    out.haveSizeBias = in.tryGetProperty(String8("touch.size.bias"), out.sizeBias);
+    out.haveSizeIsSummed = in.tryGetProperty(String8("touch.size.isSummed"), out.sizeIsSummed);
+
+    // Pressure
+    out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT;
+    String8 pressureCalibrationString;
+    if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) {
+        if (pressureCalibrationString == "none") {
+            out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+        } else if (pressureCalibrationString == "physical") {
+            out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+        } else if (pressureCalibrationString == "amplitude") {
+            out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+        } else if (pressureCalibrationString != "default") {
+            ALOGW("Invalid value for touch.pressure.calibration: '%s'",
+                  pressureCalibrationString.string());
+        }
+    }
+
+    out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"), out.pressureScale);
+
+    // Orientation
+    out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT;
+    String8 orientationCalibrationString;
+    if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) {
+        if (orientationCalibrationString == "none") {
+            out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+        } else if (orientationCalibrationString == "interpolated") {
+            out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+        } else if (orientationCalibrationString == "vector") {
+            out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR;
+        } else if (orientationCalibrationString != "default") {
+            ALOGW("Invalid value for touch.orientation.calibration: '%s'",
+                  orientationCalibrationString.string());
+        }
+    }
+
+    // Distance
+    out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_DEFAULT;
+    String8 distanceCalibrationString;
+    if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) {
+        if (distanceCalibrationString == "none") {
+            out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+        } else if (distanceCalibrationString == "scaled") {
+            out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+        } else if (distanceCalibrationString != "default") {
+            ALOGW("Invalid value for touch.distance.calibration: '%s'",
+                  distanceCalibrationString.string());
+        }
+    }
+
+    out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"), out.distanceScale);
+
+    out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_DEFAULT;
+    String8 coverageCalibrationString;
+    if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) {
+        if (coverageCalibrationString == "none") {
+            out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+        } else if (coverageCalibrationString == "box") {
+            out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_BOX;
+        } else if (coverageCalibrationString != "default") {
+            ALOGW("Invalid value for touch.coverage.calibration: '%s'",
+                  coverageCalibrationString.string());
+        }
+    }
+}
+
+void TouchInputMapper::resolveCalibration() {
+    // Size
+    if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) {
+        if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DEFAULT) {
+            mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+        }
+    } else {
+        mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+    }
+
+    // Pressure
+    if (mRawPointerAxes.pressure.valid) {
+        if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_DEFAULT) {
+            mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+        }
+    } else {
+        mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+    }
+
+    // Orientation
+    if (mRawPointerAxes.orientation.valid) {
+        if (mCalibration.orientationCalibration == Calibration::ORIENTATION_CALIBRATION_DEFAULT) {
+            mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+        }
+    } else {
+        mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+    }
+
+    // Distance
+    if (mRawPointerAxes.distance.valid) {
+        if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_DEFAULT) {
+            mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+        }
+    } else {
+        mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+    }
+
+    // Coverage
+    if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_DEFAULT) {
+        mCalibration.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+    }
+}
+
+void TouchInputMapper::dumpCalibration(std::string& dump) {
+    dump += INDENT3 "Calibration:\n";
+
+    // Size
+    switch (mCalibration.sizeCalibration) {
+        case Calibration::SIZE_CALIBRATION_NONE:
+            dump += INDENT4 "touch.size.calibration: none\n";
+            break;
+        case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+            dump += INDENT4 "touch.size.calibration: geometric\n";
+            break;
+        case Calibration::SIZE_CALIBRATION_DIAMETER:
+            dump += INDENT4 "touch.size.calibration: diameter\n";
+            break;
+        case Calibration::SIZE_CALIBRATION_BOX:
+            dump += INDENT4 "touch.size.calibration: box\n";
+            break;
+        case Calibration::SIZE_CALIBRATION_AREA:
+            dump += INDENT4 "touch.size.calibration: area\n";
+            break;
+        default:
+            ALOG_ASSERT(false);
+    }
+
+    if (mCalibration.haveSizeScale) {
+        dump += StringPrintf(INDENT4 "touch.size.scale: %0.3f\n", mCalibration.sizeScale);
+    }
+
+    if (mCalibration.haveSizeBias) {
+        dump += StringPrintf(INDENT4 "touch.size.bias: %0.3f\n", mCalibration.sizeBias);
+    }
+
+    if (mCalibration.haveSizeIsSummed) {
+        dump += StringPrintf(INDENT4 "touch.size.isSummed: %s\n",
+                             toString(mCalibration.sizeIsSummed));
+    }
+
+    // Pressure
+    switch (mCalibration.pressureCalibration) {
+        case Calibration::PRESSURE_CALIBRATION_NONE:
+            dump += INDENT4 "touch.pressure.calibration: none\n";
+            break;
+        case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+            dump += INDENT4 "touch.pressure.calibration: physical\n";
+            break;
+        case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+            dump += INDENT4 "touch.pressure.calibration: amplitude\n";
+            break;
+        default:
+            ALOG_ASSERT(false);
+    }
+
+    if (mCalibration.havePressureScale) {
+        dump += StringPrintf(INDENT4 "touch.pressure.scale: %0.3f\n", mCalibration.pressureScale);
+    }
+
+    // Orientation
+    switch (mCalibration.orientationCalibration) {
+        case Calibration::ORIENTATION_CALIBRATION_NONE:
+            dump += INDENT4 "touch.orientation.calibration: none\n";
+            break;
+        case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+            dump += INDENT4 "touch.orientation.calibration: interpolated\n";
+            break;
+        case Calibration::ORIENTATION_CALIBRATION_VECTOR:
+            dump += INDENT4 "touch.orientation.calibration: vector\n";
+            break;
+        default:
+            ALOG_ASSERT(false);
+    }
+
+    // Distance
+    switch (mCalibration.distanceCalibration) {
+        case Calibration::DISTANCE_CALIBRATION_NONE:
+            dump += INDENT4 "touch.distance.calibration: none\n";
+            break;
+        case Calibration::DISTANCE_CALIBRATION_SCALED:
+            dump += INDENT4 "touch.distance.calibration: scaled\n";
+            break;
+        default:
+            ALOG_ASSERT(false);
+    }
+
+    if (mCalibration.haveDistanceScale) {
+        dump += StringPrintf(INDENT4 "touch.distance.scale: %0.3f\n", mCalibration.distanceScale);
+    }
+
+    switch (mCalibration.coverageCalibration) {
+        case Calibration::COVERAGE_CALIBRATION_NONE:
+            dump += INDENT4 "touch.coverage.calibration: none\n";
+            break;
+        case Calibration::COVERAGE_CALIBRATION_BOX:
+            dump += INDENT4 "touch.coverage.calibration: box\n";
+            break;
+        default:
+            ALOG_ASSERT(false);
+    }
+}
+
+void TouchInputMapper::dumpAffineTransformation(std::string& dump) {
+    dump += INDENT3 "Affine Transformation:\n";
+
+    dump += StringPrintf(INDENT4 "X scale: %0.3f\n", mAffineTransform.x_scale);
+    dump += StringPrintf(INDENT4 "X ymix: %0.3f\n", mAffineTransform.x_ymix);
+    dump += StringPrintf(INDENT4 "X offset: %0.3f\n", mAffineTransform.x_offset);
+    dump += StringPrintf(INDENT4 "Y xmix: %0.3f\n", mAffineTransform.y_xmix);
+    dump += StringPrintf(INDENT4 "Y scale: %0.3f\n", mAffineTransform.y_scale);
+    dump += StringPrintf(INDENT4 "Y offset: %0.3f\n", mAffineTransform.y_offset);
+}
+
+void TouchInputMapper::updateAffineTransformation() {
+    mAffineTransform = getPolicy()->getTouchAffineTransformation(mDevice->getDescriptor(),
+                                                                 mSurfaceOrientation);
+}
+
+void TouchInputMapper::reset(nsecs_t when) {
+    mCursorButtonAccumulator.reset(getDevice());
+    mCursorScrollAccumulator.reset(getDevice());
+    mTouchButtonAccumulator.reset(getDevice());
+
+    mPointerVelocityControl.reset();
+    mWheelXVelocityControl.reset();
+    mWheelYVelocityControl.reset();
+
+    mRawStatesPending.clear();
+    mCurrentRawState.clear();
+    mCurrentCookedState.clear();
+    mLastRawState.clear();
+    mLastCookedState.clear();
+    mPointerUsage = POINTER_USAGE_NONE;
+    mSentHoverEnter = false;
+    mHavePointerIds = false;
+    mCurrentMotionAborted = false;
+    mDownTime = 0;
+
+    mCurrentVirtualKey.down = false;
+
+    mPointerGesture.reset();
+    mPointerSimple.reset();
+    resetExternalStylus();
+
+    if (mPointerController != nullptr) {
+        mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+        mPointerController->clearSpots();
+    }
+
+    InputMapper::reset(when);
+}
+
+void TouchInputMapper::resetExternalStylus() {
+    mExternalStylusState.clear();
+    mExternalStylusId = -1;
+    mExternalStylusFusionTimeout = LLONG_MAX;
+    mExternalStylusDataPending = false;
+}
+
+void TouchInputMapper::clearStylusDataPendingFlags() {
+    mExternalStylusDataPending = false;
+    mExternalStylusFusionTimeout = LLONG_MAX;
+}
+
+void TouchInputMapper::process(const RawEvent* rawEvent) {
+    mCursorButtonAccumulator.process(rawEvent);
+    mCursorScrollAccumulator.process(rawEvent);
+    mTouchButtonAccumulator.process(rawEvent);
+
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+        sync(rawEvent->when);
+    }
+}
+
+void TouchInputMapper::sync(nsecs_t when) {
+    const RawState* last =
+            mRawStatesPending.empty() ? &mCurrentRawState : &mRawStatesPending.back();
+
+    // Push a new state.
+    mRawStatesPending.emplace_back();
+
+    RawState* next = &mRawStatesPending.back();
+    next->clear();
+    next->when = when;
+
+    // Sync button state.
+    next->buttonState =
+            mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState();
+
+    // Sync scroll
+    next->rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
+    next->rawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
+    mCursorScrollAccumulator.finishSync();
+
+    // Sync touch
+    syncTouch(when, next);
+
+    // Assign pointer ids.
+    if (!mHavePointerIds) {
+        assignPointerIds(last, next);
+    }
+
+#if DEBUG_RAW_EVENTS
+    ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
+          "hovering ids 0x%08x -> 0x%08x",
+          last->rawPointerData.pointerCount, next->rawPointerData.pointerCount,
+          last->rawPointerData.touchingIdBits.value, next->rawPointerData.touchingIdBits.value,
+          last->rawPointerData.hoveringIdBits.value, next->rawPointerData.hoveringIdBits.value);
+#endif
+
+    processRawTouches(false /*timeout*/);
+}
+
+void TouchInputMapper::processRawTouches(bool timeout) {
+    if (mDeviceMode == DEVICE_MODE_DISABLED) {
+        // Drop all input if the device is disabled.
+        mCurrentRawState.clear();
+        mRawStatesPending.clear();
+        return;
+    }
+
+    // Drain any pending touch states. The invariant here is that the mCurrentRawState is always
+    // valid and must go through the full cook and dispatch cycle. This ensures that anything
+    // touching the current state will only observe the events that have been dispatched to the
+    // rest of the pipeline.
+    const size_t N = mRawStatesPending.size();
+    size_t count;
+    for (count = 0; count < N; count++) {
+        const RawState& next = mRawStatesPending[count];
+
+        // A failure to assign the stylus id means that we're waiting on stylus data
+        // and so should defer the rest of the pipeline.
+        if (assignExternalStylusId(next, timeout)) {
+            break;
+        }
+
+        // All ready to go.
+        clearStylusDataPendingFlags();
+        mCurrentRawState.copyFrom(next);
+        if (mCurrentRawState.when < mLastRawState.when) {
+            mCurrentRawState.when = mLastRawState.when;
+        }
+        cookAndDispatch(mCurrentRawState.when);
+    }
+    if (count != 0) {
+        mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count);
+    }
+
+    if (mExternalStylusDataPending) {
+        if (timeout) {
+            nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY;
+            clearStylusDataPendingFlags();
+            mCurrentRawState.copyFrom(mLastRawState);
+#if DEBUG_STYLUS_FUSION
+            ALOGD("Timeout expired, synthesizing event with new stylus data");
+#endif
+            cookAndDispatch(when);
+        } else if (mExternalStylusFusionTimeout == LLONG_MAX) {
+            mExternalStylusFusionTimeout = mExternalStylusState.when + TOUCH_DATA_TIMEOUT;
+            getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
+        }
+    }
+}
+
+void TouchInputMapper::cookAndDispatch(nsecs_t when) {
+    // Always start with a clean state.
+    mCurrentCookedState.clear();
+
+    // Apply stylus buttons to current raw state.
+    applyExternalStylusButtonState(when);
+
+    // Handle policy on initial down or hover events.
+    bool initialDown = mLastRawState.rawPointerData.pointerCount == 0 &&
+            mCurrentRawState.rawPointerData.pointerCount != 0;
+
+    uint32_t policyFlags = 0;
+    bool buttonsPressed = mCurrentRawState.buttonState & ~mLastRawState.buttonState;
+    if (initialDown || buttonsPressed) {
+        // If this is a touch screen, hide the pointer on an initial down.
+        if (mDeviceMode == DEVICE_MODE_DIRECT) {
+            getContext()->fadePointer();
+        }
+
+        if (mParameters.wake) {
+            policyFlags |= POLICY_FLAG_WAKE;
+        }
+    }
+
+    // Consume raw off-screen touches before cooking pointer data.
+    // If touches are consumed, subsequent code will not receive any pointer data.
+    if (consumeRawTouches(when, policyFlags)) {
+        mCurrentRawState.rawPointerData.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.
+    cookPointerData();
+
+    // Apply stylus pressure to current cooked state.
+    applyExternalStylusTouchState(when);
+
+    // Synthesize key down from raw buttons if needed.
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+                         mViewport.displayId, policyFlags, mLastCookedState.buttonState,
+                         mCurrentCookedState.buttonState);
+
+    // Dispatch the touches either directly or by translation through a pointer on screen.
+    if (mDeviceMode == DEVICE_MODE_POINTER) {
+        for (BitSet32 idBits(mCurrentRawState.rawPointerData.touchingIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            const RawPointerData::Pointer& pointer =
+                    mCurrentRawState.rawPointerData.pointerForId(id);
+            if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
+                pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+                mCurrentCookedState.stylusIdBits.markBit(id);
+            } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER ||
+                       pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+                mCurrentCookedState.fingerIdBits.markBit(id);
+            } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) {
+                mCurrentCookedState.mouseIdBits.markBit(id);
+            }
+        }
+        for (BitSet32 idBits(mCurrentRawState.rawPointerData.hoveringIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            const RawPointerData::Pointer& pointer =
+                    mCurrentRawState.rawPointerData.pointerForId(id);
+            if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
+                pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+                mCurrentCookedState.stylusIdBits.markBit(id);
+            }
+        }
+
+        // Stylus takes precedence over all tools, then mouse, then finger.
+        PointerUsage pointerUsage = mPointerUsage;
+        if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
+            mCurrentCookedState.mouseIdBits.clear();
+            mCurrentCookedState.fingerIdBits.clear();
+            pointerUsage = POINTER_USAGE_STYLUS;
+        } else if (!mCurrentCookedState.mouseIdBits.isEmpty()) {
+            mCurrentCookedState.fingerIdBits.clear();
+            pointerUsage = POINTER_USAGE_MOUSE;
+        } else if (!mCurrentCookedState.fingerIdBits.isEmpty() ||
+                   isPointerDown(mCurrentRawState.buttonState)) {
+            pointerUsage = POINTER_USAGE_GESTURES;
+        }
+
+        dispatchPointerUsage(when, policyFlags, pointerUsage);
+    } else {
+        if (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches &&
+            mPointerController != nullptr) {
+            mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
+            mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+
+            mPointerController->setButtonState(mCurrentRawState.buttonState);
+            mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
+                                         mCurrentCookedState.cookedPointerData.idToIndex,
+                                         mCurrentCookedState.cookedPointerData.touchingIdBits,
+                                         mViewport.displayId);
+        }
+
+        if (!mCurrentMotionAborted) {
+            dispatchButtonRelease(when, policyFlags);
+            dispatchHoverExit(when, policyFlags);
+            dispatchTouches(when, policyFlags);
+            dispatchHoverEnterAndMove(when, policyFlags);
+            dispatchButtonPress(when, policyFlags);
+        }
+
+        if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
+            mCurrentMotionAborted = false;
+        }
+    }
+
+    // Synthesize key up from raw buttons if needed.
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+                         mViewport.displayId, policyFlags, mLastCookedState.buttonState,
+                         mCurrentCookedState.buttonState);
+
+    // 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);
+}
+
+void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) {
+    if (mDeviceMode == DEVICE_MODE_DIRECT && hasExternalStylus() && mExternalStylusId != -1) {
+        mCurrentRawState.buttonState |= mExternalStylusState.buttons;
+    }
+}
+
+void TouchInputMapper::applyExternalStylusTouchState(nsecs_t when) {
+    CookedPointerData& currentPointerData = mCurrentCookedState.cookedPointerData;
+    const CookedPointerData& lastPointerData = mLastCookedState.cookedPointerData;
+
+    if (mExternalStylusId != -1 && currentPointerData.isTouching(mExternalStylusId)) {
+        float pressure = mExternalStylusState.pressure;
+        if (pressure == 0.0f && lastPointerData.isTouching(mExternalStylusId)) {
+            const PointerCoords& coords = lastPointerData.pointerCoordsForId(mExternalStylusId);
+            pressure = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+        }
+        PointerCoords& coords = currentPointerData.editPointerCoordsWithId(mExternalStylusId);
+        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+
+        PointerProperties& properties =
+                currentPointerData.editPointerPropertiesWithId(mExternalStylusId);
+        if (mExternalStylusState.toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+            properties.toolType = mExternalStylusState.toolType;
+        }
+    }
+}
+
+bool TouchInputMapper::assignExternalStylusId(const RawState& state, bool timeout) {
+    if (mDeviceMode != DEVICE_MODE_DIRECT || !hasExternalStylus()) {
+        return false;
+    }
+
+    const bool initialDown = mLastRawState.rawPointerData.pointerCount == 0 &&
+            state.rawPointerData.pointerCount != 0;
+    if (initialDown) {
+        if (mExternalStylusState.pressure != 0.0f) {
+#if DEBUG_STYLUS_FUSION
+            ALOGD("Have both stylus and touch data, beginning fusion");
+#endif
+            mExternalStylusId = state.rawPointerData.touchingIdBits.firstMarkedBit();
+        } else if (timeout) {
+#if DEBUG_STYLUS_FUSION
+            ALOGD("Timeout expired, assuming touch is not a stylus.");
+#endif
+            resetExternalStylus();
+        } else {
+            if (mExternalStylusFusionTimeout == LLONG_MAX) {
+                mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT;
+            }
+#if DEBUG_STYLUS_FUSION
+            ALOGD("No stylus data but stylus is connected, requesting timeout "
+                  "(%" PRId64 "ms)",
+                  mExternalStylusFusionTimeout);
+#endif
+            getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
+            return true;
+        }
+    }
+
+    // Check if the stylus pointer has gone up.
+    if (mExternalStylusId != -1 && !state.rawPointerData.touchingIdBits.hasBit(mExternalStylusId)) {
+#if DEBUG_STYLUS_FUSION
+        ALOGD("Stylus pointer is going up");
+#endif
+        mExternalStylusId = -1;
+    }
+
+    return false;
+}
+
+void TouchInputMapper::timeoutExpired(nsecs_t when) {
+    if (mDeviceMode == DEVICE_MODE_POINTER) {
+        if (mPointerUsage == POINTER_USAGE_GESTURES) {
+            dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
+        }
+    } else if (mDeviceMode == DEVICE_MODE_DIRECT) {
+        if (mExternalStylusFusionTimeout < when) {
+            processRawTouches(true /*timeout*/);
+        } else if (mExternalStylusFusionTimeout != LLONG_MAX) {
+            getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
+        }
+    }
+}
+
+void TouchInputMapper::updateExternalStylusState(const StylusState& state) {
+    mExternalStylusState.copyFrom(state);
+    if (mExternalStylusId != -1 || mExternalStylusFusionTimeout != LLONG_MAX) {
+        // We're either in the middle of a fused stream of data or we're waiting on data before
+        // dispatching the initial down, so go ahead and dispatch now that we have fresh stylus
+        // data.
+        mExternalStylusDataPending = true;
+        processRawTouches(false /*timeout*/);
+    }
+}
+
+bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) {
+    // Check for release of a virtual key.
+    if (mCurrentVirtualKey.down) {
+        if (mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
+            // Pointer went up while virtual key was down.
+            mCurrentVirtualKey.down = false;
+            if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+                ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+                      mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+                dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_UP,
+                                   AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+            }
+            return true;
+        }
+
+        if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
+            uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit();
+            const RawPointerData::Pointer& pointer =
+                    mCurrentRawState.rawPointerData.pointerForId(id);
+            const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+            if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) {
+                // Pointer is still within the space of the virtual key.
+                return true;
+            }
+        }
+
+        // Pointer left virtual key area or another pointer also went down.
+        // Send key cancellation but do not consume the touch yet.
+        // This is useful when the user swipes through from the virtual key area
+        // into the main display surface.
+        mCurrentVirtualKey.down = false;
+        if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+            ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", mCurrentVirtualKey.keyCode,
+                  mCurrentVirtualKey.scanCode);
+#endif
+            dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_UP,
+                               AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY |
+                                       AKEY_EVENT_FLAG_CANCELED);
+        }
+    }
+
+    if (mLastRawState.rawPointerData.touchingIdBits.isEmpty() &&
+        !mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
+        // Pointer just went down.  Check for virtual key press or off-screen touches.
+        uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit();
+        const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id);
+        if (!isPointInsideSurface(pointer.x, pointer.y)) {
+            // If exactly one pointer went down, check for virtual key hit.
+            // Otherwise we will drop the entire stroke.
+            if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
+                const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+                if (virtualKey) {
+                    mCurrentVirtualKey.down = true;
+                    mCurrentVirtualKey.downTime = when;
+                    mCurrentVirtualKey.keyCode = virtualKey->keyCode;
+                    mCurrentVirtualKey.scanCode = virtualKey->scanCode;
+                    mCurrentVirtualKey.ignored =
+                            mContext->shouldDropVirtualKey(when, getDevice(), virtualKey->keyCode,
+                                                           virtualKey->scanCode);
+
+                    if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+                        ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+                              mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+                        dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_DOWN,
+                                           AKEY_EVENT_FLAG_FROM_SYSTEM |
+                                                   AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    // Disable all virtual key touches that happen within a short time interval of the
+    // most recent touch within the screen area.  The idea is to filter out stray
+    // virtual key presses when interacting with the touch screen.
+    //
+    // Problems we're trying to solve:
+    //
+    // 1. While scrolling a list or dragging the window shade, the user swipes down into a
+    //    virtual key area that is implemented by a separate touch panel and accidentally
+    //    triggers a virtual key.
+    //
+    // 2. While typing in the on screen keyboard, the user taps slightly outside the screen
+    //    area and accidentally triggers a virtual key.  This often happens when virtual keys
+    //    are layed out below the screen near to where the on screen keyboard's space bar
+    //    is displayed.
+    if (mConfig.virtualKeyQuietTime > 0 &&
+        !mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
+        mContext->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime);
+    }
+    return false;
+}
+
+void TouchInputMapper::dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+                                          int32_t keyEventAction, int32_t keyEventFlags) {
+    int32_t keyCode = mCurrentVirtualKey.keyCode;
+    int32_t scanCode = mCurrentVirtualKey.scanCode;
+    nsecs_t downTime = mCurrentVirtualKey.downTime;
+    int32_t metaState = mContext->getGlobalMetaState();
+    policyFlags |= POLICY_FLAG_VIRTUAL;
+
+    NotifyKeyArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), AINPUT_SOURCE_KEYBOARD,
+                       mViewport.displayId, policyFlags, keyEventAction, keyEventFlags, keyCode,
+                       scanCode, metaState, downTime);
+    getListener()->notifyKey(&args);
+}
+
+void TouchInputMapper::abortTouches(nsecs_t when, uint32_t policyFlags) {
+    BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
+    if (!currentIdBits.isEmpty()) {
+        int32_t metaState = getContext()->getGlobalMetaState();
+        int32_t buttonState = mCurrentCookedState.buttonState;
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
+                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                       mCurrentCookedState.cookedPointerData.pointerProperties,
+                       mCurrentCookedState.cookedPointerData.pointerCoords,
+                       mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1,
+                       mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+        mCurrentMotionAborted = true;
+    }
+}
+
+void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+    BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
+    BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
+    int32_t metaState = getContext()->getGlobalMetaState();
+    int32_t buttonState = mCurrentCookedState.buttonState;
+
+    if (currentIdBits == lastIdBits) {
+        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.
+            dispatchMotion(when, 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);
+        }
+    } else {
+        // There may be pointers going up and pointers going down and pointers moving
+        // all at the same time.
+        BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
+        BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
+        BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
+        BitSet32 dispatchedIdBits(lastIdBits.value);
+
+        // Update last coordinates of pointers that have moved so that we observe the new
+        // pointer positions at the same time as other pointers that have just gone up.
+        bool moveNeeded =
+                updateMovedPointers(mCurrentCookedState.cookedPointerData.pointerProperties,
+                                    mCurrentCookedState.cookedPointerData.pointerCoords,
+                                    mCurrentCookedState.cookedPointerData.idToIndex,
+                                    mLastCookedState.cookedPointerData.pointerProperties,
+                                    mLastCookedState.cookedPointerData.pointerCoords,
+                                    mLastCookedState.cookedPointerData.idToIndex, moveIdBits);
+        if (buttonState != mLastCookedState.buttonState) {
+            moveNeeded = true;
+        }
+
+        // Dispatch pointer up events.
+        while (!upIdBits.isEmpty()) {
+            uint32_t upId = upIdBits.clearFirstMarkedBit();
+
+            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, 0,
+                           metaState, buttonState, 0,
+                           mLastCookedState.cookedPointerData.pointerProperties,
+                           mLastCookedState.cookedPointerData.pointerCoords,
+                           mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId,
+                           mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+            dispatchedIdBits.clearBit(upId);
+        }
+
+        // Dispatch move events if any of the remaining pointers moved from their old locations.
+        // Although applications receive new locations as part of individual pointer up
+        // events, they do not generally handle them except when presented in a move event.
+        if (moveNeeded && !moveIdBits.isEmpty()) {
+            ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
+            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
+                           buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties,
+                           mCurrentCookedState.cookedPointerData.pointerCoords,
+                           mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1,
+                           mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+        }
+
+        // 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;
+            }
+
+            dispatchMotion(when, 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);
+        }
+    }
+}
+
+void TouchInputMapper::dispatchHoverExit(nsecs_t when, uint32_t policyFlags) {
+    if (mSentHoverEnter &&
+        (mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty() ||
+         !mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty())) {
+        int32_t metaState = getContext()->getGlobalMetaState();
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState,
+                       mLastCookedState.buttonState, 0,
+                       mLastCookedState.cookedPointerData.pointerProperties,
+                       mLastCookedState.cookedPointerData.pointerCoords,
+                       mLastCookedState.cookedPointerData.idToIndex,
+                       mLastCookedState.cookedPointerData.hoveringIdBits, -1, mOrientedXPrecision,
+                       mOrientedYPrecision, mDownTime);
+        mSentHoverEnter = false;
+    }
+}
+
+void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags) {
+    if (mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty() &&
+        !mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty()) {
+        int32_t metaState = getContext()->getGlobalMetaState();
+        if (!mSentHoverEnter) {
+            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0,
+                           metaState, mCurrentRawState.buttonState, 0,
+                           mCurrentCookedState.cookedPointerData.pointerProperties,
+                           mCurrentCookedState.cookedPointerData.pointerCoords,
+                           mCurrentCookedState.cookedPointerData.idToIndex,
+                           mCurrentCookedState.cookedPointerData.hoveringIdBits, -1,
+                           mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+            mSentHoverEnter = true;
+        }
+
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
+                       mCurrentRawState.buttonState, 0,
+                       mCurrentCookedState.cookedPointerData.pointerProperties,
+                       mCurrentCookedState.cookedPointerData.pointerCoords,
+                       mCurrentCookedState.cookedPointerData.idToIndex,
+                       mCurrentCookedState.cookedPointerData.hoveringIdBits, -1,
+                       mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+    }
+}
+
+void TouchInputMapper::dispatchButtonRelease(nsecs_t when, uint32_t policyFlags) {
+    BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState);
+    const BitSet32& idBits = findActiveIdBits(mLastCookedState.cookedPointerData);
+    const int32_t metaState = getContext()->getGlobalMetaState();
+    int32_t buttonState = mLastCookedState.buttonState;
+    while (!releasedButtons.isEmpty()) {
+        int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit());
+        buttonState &= ~actionButton;
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                       actionButton, 0, metaState, buttonState, 0,
+                       mCurrentCookedState.cookedPointerData.pointerProperties,
+                       mCurrentCookedState.cookedPointerData.pointerCoords,
+                       mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1,
+                       mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+    }
+}
+
+void TouchInputMapper::dispatchButtonPress(nsecs_t when, uint32_t policyFlags) {
+    BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState);
+    const BitSet32& idBits = findActiveIdBits(mCurrentCookedState.cookedPointerData);
+    const int32_t metaState = getContext()->getGlobalMetaState();
+    int32_t buttonState = mLastCookedState.buttonState;
+    while (!pressedButtons.isEmpty()) {
+        int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit());
+        buttonState |= actionButton;
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton,
+                       0, metaState, buttonState, 0,
+                       mCurrentCookedState.cookedPointerData.pointerProperties,
+                       mCurrentCookedState.cookedPointerData.pointerCoords,
+                       mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1,
+                       mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+    }
+}
+
+const BitSet32& TouchInputMapper::findActiveIdBits(const CookedPointerData& cookedPointerData) {
+    if (!cookedPointerData.touchingIdBits.isEmpty()) {
+        return cookedPointerData.touchingIdBits;
+    }
+    return cookedPointerData.hoveringIdBits;
+}
+
+void TouchInputMapper::cookPointerData() {
+    uint32_t currentPointerCount = mCurrentRawState.rawPointerData.pointerCount;
+
+    mCurrentCookedState.cookedPointerData.clear();
+    mCurrentCookedState.cookedPointerData.pointerCount = currentPointerCount;
+    mCurrentCookedState.cookedPointerData.hoveringIdBits =
+            mCurrentRawState.rawPointerData.hoveringIdBits;
+    mCurrentCookedState.cookedPointerData.touchingIdBits =
+            mCurrentRawState.rawPointerData.touchingIdBits;
+
+    if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
+        mCurrentCookedState.buttonState = 0;
+    } else {
+        mCurrentCookedState.buttonState = mCurrentRawState.buttonState;
+    }
+
+    // Walk through the the active pointers and map device coordinates onto
+    // surface coordinates and adjust for display orientation.
+    for (uint32_t i = 0; i < currentPointerCount; i++) {
+        const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];
+
+        // Size
+        float touchMajor, touchMinor, toolMajor, toolMinor, size;
+        switch (mCalibration.sizeCalibration) {
+            case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+            case Calibration::SIZE_CALIBRATION_DIAMETER:
+            case Calibration::SIZE_CALIBRATION_BOX:
+            case Calibration::SIZE_CALIBRATION_AREA:
+                if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) {
+                    touchMajor = in.touchMajor;
+                    touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
+                    toolMajor = in.toolMajor;
+                    toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
+                    size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
+                                                            : in.touchMajor;
+                } else if (mRawPointerAxes.touchMajor.valid) {
+                    toolMajor = touchMajor = in.touchMajor;
+                    toolMinor = touchMinor =
+                            mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
+                    size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
+                                                            : in.touchMajor;
+                } else if (mRawPointerAxes.toolMajor.valid) {
+                    touchMajor = toolMajor = in.toolMajor;
+                    touchMinor = toolMinor =
+                            mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
+                    size = mRawPointerAxes.toolMinor.valid ? avg(in.toolMajor, in.toolMinor)
+                                                           : in.toolMajor;
+                } else {
+                    ALOG_ASSERT(false,
+                                "No touch or tool axes.  "
+                                "Size calibration should have been resolved to NONE.");
+                    touchMajor = 0;
+                    touchMinor = 0;
+                    toolMajor = 0;
+                    toolMinor = 0;
+                    size = 0;
+                }
+
+                if (mCalibration.haveSizeIsSummed && mCalibration.sizeIsSummed) {
+                    uint32_t touchingCount = mCurrentRawState.rawPointerData.touchingIdBits.count();
+                    if (touchingCount > 1) {
+                        touchMajor /= touchingCount;
+                        touchMinor /= touchingCount;
+                        toolMajor /= touchingCount;
+                        toolMinor /= touchingCount;
+                        size /= touchingCount;
+                    }
+                }
+
+                if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_GEOMETRIC) {
+                    touchMajor *= mGeometricScale;
+                    touchMinor *= mGeometricScale;
+                    toolMajor *= mGeometricScale;
+                    toolMinor *= mGeometricScale;
+                } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_AREA) {
+                    touchMajor = touchMajor > 0 ? sqrtf(touchMajor) : 0;
+                    touchMinor = touchMajor;
+                    toolMajor = toolMajor > 0 ? sqrtf(toolMajor) : 0;
+                    toolMinor = toolMajor;
+                } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DIAMETER) {
+                    touchMinor = touchMajor;
+                    toolMinor = toolMajor;
+                }
+
+                mCalibration.applySizeScaleAndBias(&touchMajor);
+                mCalibration.applySizeScaleAndBias(&touchMinor);
+                mCalibration.applySizeScaleAndBias(&toolMajor);
+                mCalibration.applySizeScaleAndBias(&toolMinor);
+                size *= mSizeScale;
+                break;
+            default:
+                touchMajor = 0;
+                touchMinor = 0;
+                toolMajor = 0;
+                toolMinor = 0;
+                size = 0;
+                break;
+        }
+
+        // Pressure
+        float pressure;
+        switch (mCalibration.pressureCalibration) {
+            case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+            case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+                pressure = in.pressure * mPressureScale;
+                break;
+            default:
+                pressure = in.isHovering ? 0 : 1;
+                break;
+        }
+
+        // Tilt and Orientation
+        float tilt;
+        float orientation;
+        if (mHaveTilt) {
+            float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
+            float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
+            orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
+            tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
+        } else {
+            tilt = 0;
+
+            switch (mCalibration.orientationCalibration) {
+                case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+                    orientation = in.orientation * mOrientationScale;
+                    break;
+                case Calibration::ORIENTATION_CALIBRATION_VECTOR: {
+                    int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
+                    int32_t c2 = signExtendNybble(in.orientation & 0x0f);
+                    if (c1 != 0 || c2 != 0) {
+                        orientation = atan2f(c1, c2) * 0.5f;
+                        float confidence = hypotf(c1, c2);
+                        float scale = 1.0f + confidence / 16.0f;
+                        touchMajor *= scale;
+                        touchMinor /= scale;
+                        toolMajor *= scale;
+                        toolMinor /= scale;
+                    } else {
+                        orientation = 0;
+                    }
+                    break;
+                }
+                default:
+                    orientation = 0;
+            }
+        }
+
+        // Distance
+        float distance;
+        switch (mCalibration.distanceCalibration) {
+            case Calibration::DISTANCE_CALIBRATION_SCALED:
+                distance = in.distance * mDistanceScale;
+                break;
+            default:
+                distance = 0;
+        }
+
+        // Coverage
+        int32_t rawLeft, rawTop, rawRight, rawBottom;
+        switch (mCalibration.coverageCalibration) {
+            case Calibration::COVERAGE_CALIBRATION_BOX:
+                rawLeft = (in.toolMinor & 0xffff0000) >> 16;
+                rawRight = in.toolMinor & 0x0000ffff;
+                rawBottom = in.toolMajor & 0x0000ffff;
+                rawTop = (in.toolMajor & 0xffff0000) >> 16;
+                break;
+            default:
+                rawLeft = rawTop = rawRight = rawBottom = 0;
+                break;
+        }
+
+        // Adjust X,Y coords for device calibration
+        // TODO: Adjust coverage coords?
+        float xTransformed = in.x, yTransformed = in.y;
+        mAffineTransform.applyTo(xTransformed, yTransformed);
+
+        // Adjust X, Y, and coverage coords for surface orientation.
+        float x, y;
+        float left, top, right, bottom;
+
+        switch (mSurfaceOrientation) {
+            case DISPLAY_ORIENTATION_90:
+                x = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+                y = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale + mXTranslate;
+                left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+                right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+                bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+                top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+                orientation -= M_PI_2;
+                if (mOrientedRanges.haveOrientation &&
+                    orientation < mOrientedRanges.orientation.min) {
+                    orientation +=
+                            (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
+                }
+                break;
+            case DISPLAY_ORIENTATION_180:
+                x = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale;
+                y = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale + mYTranslate;
+                left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
+                right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
+                bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+                top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+                orientation -= M_PI;
+                if (mOrientedRanges.haveOrientation &&
+                    orientation < mOrientedRanges.orientation.min) {
+                    orientation +=
+                            (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
+                }
+                break;
+            case DISPLAY_ORIENTATION_270:
+                x = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale;
+                y = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+                left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
+                right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
+                bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+                top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+                orientation += M_PI_2;
+                if (mOrientedRanges.haveOrientation &&
+                    orientation > mOrientedRanges.orientation.max) {
+                    orientation -=
+                            (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
+                }
+                break;
+            default:
+                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;
+                break;
+        }
+
+        // Write output coords.
+        PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
+        out.clear();
+        out.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        out.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+        out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
+        out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
+        out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor);
+        out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
+        out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
+        out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
+        if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
+            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
+            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
+            out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
+        } else {
+            out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
+            out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
+        }
+
+        // Write output properties.
+        PointerProperties& properties = mCurrentCookedState.cookedPointerData.pointerProperties[i];
+        uint32_t id = in.id;
+        properties.clear();
+        properties.id = id;
+        properties.toolType = in.toolType;
+
+        // Write id index.
+        mCurrentCookedState.cookedPointerData.idToIndex[id] = i;
+    }
+}
+
+void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags,
+                                            PointerUsage pointerUsage) {
+    if (pointerUsage != mPointerUsage) {
+        abortPointerUsage(when, policyFlags);
+        mPointerUsage = pointerUsage;
+    }
+
+    switch (mPointerUsage) {
+        case POINTER_USAGE_GESTURES:
+            dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
+            break;
+        case POINTER_USAGE_STYLUS:
+            dispatchPointerStylus(when, policyFlags);
+            break;
+        case POINTER_USAGE_MOUSE:
+            dispatchPointerMouse(when, policyFlags);
+            break;
+        default:
+            break;
+    }
+}
+
+void TouchInputMapper::abortPointerUsage(nsecs_t when, uint32_t policyFlags) {
+    switch (mPointerUsage) {
+        case POINTER_USAGE_GESTURES:
+            abortPointerGestures(when, policyFlags);
+            break;
+        case POINTER_USAGE_STYLUS:
+            abortPointerStylus(when, policyFlags);
+            break;
+        case POINTER_USAGE_MOUSE:
+            abortPointerMouse(when, policyFlags);
+            break;
+        default:
+            break;
+    }
+
+    mPointerUsage = POINTER_USAGE_NONE;
+}
+
+void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout) {
+    // Update current gesture coordinates.
+    bool cancelPreviousGesture, finishPreviousGesture;
+    bool sendEvents =
+            preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
+    if (!sendEvents) {
+        return;
+    }
+    if (finishPreviousGesture) {
+        cancelPreviousGesture = false;
+    }
+
+    // Update the pointer presentation and spots.
+    if (mParameters.gestureMode == Parameters::GESTURE_MODE_MULTI_TOUCH) {
+        mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+        if (finishPreviousGesture || cancelPreviousGesture) {
+            mPointerController->clearSpots();
+        }
+
+        if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
+            mPointerController->setSpots(mPointerGesture.currentGestureCoords,
+                                         mPointerGesture.currentGestureIdToIndex,
+                                         mPointerGesture.currentGestureIdBits,
+                                         mPointerController->getDisplayId());
+        }
+    } else {
+        mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+    }
+
+    // Show or hide the pointer if needed.
+    switch (mPointerGesture.currentGestureMode) {
+        case PointerGesture::NEUTRAL:
+        case PointerGesture::QUIET:
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_MULTI_TOUCH &&
+                mPointerGesture.lastGestureMode == PointerGesture::FREEFORM) {
+                // Remind the user of where the pointer is after finishing a gesture with spots.
+                mPointerController->unfade(PointerControllerInterface::TRANSITION_GRADUAL);
+            }
+            break;
+        case PointerGesture::TAP:
+        case PointerGesture::TAP_DRAG:
+        case PointerGesture::BUTTON_CLICK_OR_DRAG:
+        case PointerGesture::HOVER:
+        case PointerGesture::PRESS:
+        case PointerGesture::SWIPE:
+            // Unfade the pointer when the current gesture manipulates the
+            // area directly under the pointer.
+            mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+            break;
+        case PointerGesture::FREEFORM:
+            // Fade the pointer when the current gesture manipulates a different
+            // area and there are spots to guide the user experience.
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_MULTI_TOUCH) {
+                mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+            } else {
+                mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+            }
+            break;
+    }
+
+    // Send events!
+    int32_t metaState = getContext()->getGlobalMetaState();
+    int32_t buttonState = mCurrentCookedState.buttonState;
+
+    // Update last coordinates of pointers that have moved so that we observe the new
+    // pointer positions at the same time as other pointers that have just gone up.
+    bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP ||
+            mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG ||
+            mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG ||
+            mPointerGesture.currentGestureMode == PointerGesture::PRESS ||
+            mPointerGesture.currentGestureMode == PointerGesture::SWIPE ||
+            mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
+    bool moveNeeded = false;
+    if (down && !cancelPreviousGesture && !finishPreviousGesture &&
+        !mPointerGesture.lastGestureIdBits.isEmpty() &&
+        !mPointerGesture.currentGestureIdBits.isEmpty()) {
+        BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value &
+                                    mPointerGesture.lastGestureIdBits.value);
+        moveNeeded = updateMovedPointers(mPointerGesture.currentGestureProperties,
+                                         mPointerGesture.currentGestureCoords,
+                                         mPointerGesture.currentGestureIdToIndex,
+                                         mPointerGesture.lastGestureProperties,
+                                         mPointerGesture.lastGestureCoords,
+                                         mPointerGesture.lastGestureIdToIndex, movedGestureIdBits);
+        if (buttonState != mLastCookedState.buttonState) {
+            moveNeeded = true;
+        }
+    }
+
+    // Send motion events for all pointers that went up or were canceled.
+    BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
+    if (!dispatchedGestureIdBits.isEmpty()) {
+        if (cancelPreviousGesture) {
+            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
+                           buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                           mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
+                           mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
+                           mPointerGesture.downTime);
+
+            dispatchedGestureIdBits.clear();
+        } else {
+            BitSet32 upGestureIdBits;
+            if (finishPreviousGesture) {
+                upGestureIdBits = dispatchedGestureIdBits;
+            } else {
+                upGestureIdBits.value =
+                        dispatchedGestureIdBits.value & ~mPointerGesture.currentGestureIdBits.value;
+            }
+            while (!upGestureIdBits.isEmpty()) {
+                uint32_t id = upGestureIdBits.clearFirstMarkedBit();
+
+                dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, 0,
+                               metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                               mPointerGesture.lastGestureProperties,
+                               mPointerGesture.lastGestureCoords,
+                               mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0,
+                               0, mPointerGesture.downTime);
+
+                dispatchedGestureIdBits.clearBit(id);
+            }
+        }
+    }
+
+    // Send motion events for all pointers that moved.
+    if (moveNeeded) {
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
+                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                       mPointerGesture.currentGestureProperties,
+                       mPointerGesture.currentGestureCoords,
+                       mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
+                       mPointerGesture.downTime);
+    }
+
+    // Send motion events for all pointers that went down.
+    if (down) {
+        BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value &
+                                   ~dispatchedGestureIdBits.value);
+        while (!downGestureIdBits.isEmpty()) {
+            uint32_t id = downGestureIdBits.clearFirstMarkedBit();
+            dispatchedGestureIdBits.markBit(id);
+
+            if (dispatchedGestureIdBits.count() == 1) {
+                mPointerGesture.downTime = when;
+            }
+
+            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0,
+                           metaState, buttonState, 0, mPointerGesture.currentGestureProperties,
+                           mPointerGesture.currentGestureCoords,
+                           mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0,
+                           0, mPointerGesture.downTime);
+        }
+    }
+
+    // Send motion events for hover.
+    if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) {
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
+                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                       mPointerGesture.currentGestureProperties,
+                       mPointerGesture.currentGestureCoords,
+                       mPointerGesture.currentGestureIdToIndex,
+                       mPointerGesture.currentGestureIdBits, -1, 0, 0, mPointerGesture.downTime);
+    } else if (dispatchedGestureIdBits.isEmpty() && !mPointerGesture.lastGestureIdBits.isEmpty()) {
+        // Synthesize a hover move event after all pointers go up to indicate that
+        // the pointer is hovering again even if the user is not currently touching
+        // the touch pad.  This ensures that a view will receive a fresh hover enter
+        // event after a tap.
+        float x, y;
+        mPointerController->getPosition(&x, &y);
+
+        PointerProperties pointerProperties;
+        pointerProperties.clear();
+        pointerProperties.id = 0;
+        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+        PointerCoords pointerCoords;
+        pointerCoords.clear();
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+
+        const int32_t displayId = mPointerController->getDisplayId();
+        NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
+                              metaState, buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
+                              0, 0, x, y, mPointerGesture.downTime, /* videoFrames */ {});
+        getListener()->notifyMotion(&args);
+    }
+
+    // Update state.
+    mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
+    if (!down) {
+        mPointerGesture.lastGestureIdBits.clear();
+    } else {
+        mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
+        for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+            mPointerGesture.lastGestureProperties[index].copyFrom(
+                    mPointerGesture.currentGestureProperties[index]);
+            mPointerGesture.lastGestureCoords[index].copyFrom(
+                    mPointerGesture.currentGestureCoords[index]);
+            mPointerGesture.lastGestureIdToIndex[id] = index;
+        }
+    }
+}
+
+void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) {
+    // Cancel previously dispatches pointers.
+    if (!mPointerGesture.lastGestureIdBits.isEmpty()) {
+        int32_t metaState = getContext()->getGlobalMetaState();
+        int32_t buttonState = mCurrentRawState.buttonState;
+        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
+                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                       mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
+                       mPointerGesture.lastGestureIdToIndex, mPointerGesture.lastGestureIdBits, -1,
+                       0, 0, mPointerGesture.downTime);
+    }
+
+    // Reset the current pointer gesture.
+    mPointerGesture.reset();
+    mPointerVelocityControl.reset();
+
+    // Remove any current spots.
+    if (mPointerController != nullptr) {
+        mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+        mPointerController->clearSpots();
+    }
+}
+
+bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
+                                              bool* outFinishPreviousGesture, bool isTimeout) {
+    *outCancelPreviousGesture = false;
+    *outFinishPreviousGesture = false;
+
+    // Handle TAP timeout.
+    if (isTimeout) {
+#if DEBUG_GESTURES
+        ALOGD("Gestures: Processing timeout");
+#endif
+
+        if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+            if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+                // The tap/drag timeout has not yet expired.
+                getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime +
+                                                   mConfig.pointerGestureTapDragInterval);
+            } else {
+                // The tap is finished.
+#if DEBUG_GESTURES
+                ALOGD("Gestures: TAP finished");
+#endif
+                *outFinishPreviousGesture = true;
+
+                mPointerGesture.activeGestureId = -1;
+                mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+                mPointerGesture.currentGestureIdBits.clear();
+
+                mPointerVelocityControl.reset();
+                return true;
+            }
+        }
+
+        // We did not handle this timeout.
+        return false;
+    }
+
+    const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
+    const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count();
+
+    // Update the velocity tracker.
+    {
+        VelocityTracker::Position positions[MAX_POINTERS];
+        uint32_t count = 0;
+        for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty(); count++) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            const RawPointerData::Pointer& pointer =
+                    mCurrentRawState.rawPointerData.pointerForId(id);
+            positions[count].x = pointer.x * mPointerXMovementScale;
+            positions[count].y = pointer.y * mPointerYMovementScale;
+        }
+        mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits,
+                                                    positions);
+    }
+
+    // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning
+    // to NEUTRAL, then we should not generate tap event.
+    if (mPointerGesture.lastGestureMode != PointerGesture::HOVER &&
+        mPointerGesture.lastGestureMode != PointerGesture::TAP &&
+        mPointerGesture.lastGestureMode != PointerGesture::TAP_DRAG) {
+        mPointerGesture.resetTap();
+    }
+
+    // Pick a new active touch id if needed.
+    // Choose an arbitrary pointer that just went down, if there is one.
+    // Otherwise choose an arbitrary remaining pointer.
+    // This guarantees we always have an active touch id when there is at least one pointer.
+    // We keep the same active touch id for as long as possible.
+    int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
+    int32_t activeTouchId = lastActiveTouchId;
+    if (activeTouchId < 0) {
+        if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
+            activeTouchId = mPointerGesture.activeTouchId =
+                    mCurrentCookedState.fingerIdBits.firstMarkedBit();
+            mPointerGesture.firstTouchTime = when;
+        }
+    } else if (!mCurrentCookedState.fingerIdBits.hasBit(activeTouchId)) {
+        if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
+            activeTouchId = mPointerGesture.activeTouchId =
+                    mCurrentCookedState.fingerIdBits.firstMarkedBit();
+        } else {
+            activeTouchId = mPointerGesture.activeTouchId = -1;
+        }
+    }
+
+    // Determine whether we are in quiet time.
+    bool isQuietTime = false;
+    if (activeTouchId < 0) {
+        mPointerGesture.resetQuietTime();
+    } else {
+        isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
+        if (!isQuietTime) {
+            if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS ||
+                 mPointerGesture.lastGestureMode == PointerGesture::SWIPE ||
+                 mPointerGesture.lastGestureMode == PointerGesture::FREEFORM) &&
+                currentFingerCount < 2) {
+                // Enter quiet time when exiting swipe or freeform state.
+                // This is to prevent accidentally entering the hover state and flinging the
+                // pointer when finishing a swipe and there is still one pointer left onscreen.
+                isQuietTime = true;
+            } else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG &&
+                       currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
+                // Enter quiet time when releasing the button and there are still two or more
+                // fingers down.  This may indicate that one finger was used to press the button
+                // but it has not gone up yet.
+                isQuietTime = true;
+            }
+            if (isQuietTime) {
+                mPointerGesture.quietTime = when;
+            }
+        }
+    }
+
+    // Switch states based on button and pointer state.
+    if (isQuietTime) {
+        // Case 1: Quiet time. (QUIET)
+#if DEBUG_GESTURES
+        ALOGD("Gestures: QUIET for next %0.3fms",
+              (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * 0.000001f);
+#endif
+        if (mPointerGesture.lastGestureMode != PointerGesture::QUIET) {
+            *outFinishPreviousGesture = true;
+        }
+
+        mPointerGesture.activeGestureId = -1;
+        mPointerGesture.currentGestureMode = PointerGesture::QUIET;
+        mPointerGesture.currentGestureIdBits.clear();
+
+        mPointerVelocityControl.reset();
+    } else if (isPointerDown(mCurrentRawState.buttonState)) {
+        // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG)
+        // The pointer follows the active touch point.
+        // Emit DOWN, MOVE, UP events at the pointer location.
+        //
+        // Only the active touch matters; other fingers are ignored.  This policy helps
+        // to handle the case where the user places a second finger on the touch pad
+        // to apply the necessary force to depress an integrated button below the surface.
+        // We don't want the second finger to be delivered to applications.
+        //
+        // For this to work well, we need to make sure to track the pointer that is really
+        // active.  If the user first puts one finger down to click then adds another
+        // finger to drag then the active pointer should switch to the finger that is
+        // being dragged.
+#if DEBUG_GESTURES
+        ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
+              "currentFingerCount=%d",
+              activeTouchId, currentFingerCount);
+#endif
+        // Reset state when just starting.
+        if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) {
+            *outFinishPreviousGesture = true;
+            mPointerGesture.activeGestureId = 0;
+        }
+
+        // Switch pointers if needed.
+        // Find the fastest pointer and follow it.
+        if (activeTouchId >= 0 && currentFingerCount > 1) {
+            int32_t bestId = -1;
+            float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
+            for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
+                uint32_t id = idBits.clearFirstMarkedBit();
+                float vx, vy;
+                if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
+                    float speed = hypotf(vx, vy);
+                    if (speed > bestSpeed) {
+                        bestId = id;
+                        bestSpeed = speed;
+                    }
+                }
+            }
+            if (bestId >= 0 && bestId != activeTouchId) {
+                mPointerGesture.activeTouchId = activeTouchId = bestId;
+#if DEBUG_GESTURES
+                ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
+                      "bestId=%d, bestSpeed=%0.3f",
+                      bestId, bestSpeed);
+#endif
+            }
+        }
+
+        float deltaX = 0, deltaY = 0;
+        if (activeTouchId >= 0 && mLastCookedState.fingerIdBits.hasBit(activeTouchId)) {
+            const RawPointerData::Pointer& currentPointer =
+                    mCurrentRawState.rawPointerData.pointerForId(activeTouchId);
+            const RawPointerData::Pointer& lastPointer =
+                    mLastRawState.rawPointerData.pointerForId(activeTouchId);
+            deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale;
+            deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale;
+
+            rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+            mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+            // Move the pointer using a relative motion.
+            // When using spots, the click will occur at the position of the anchor
+            // spot and all other spots will move there.
+            mPointerController->move(deltaX, deltaY);
+        } else {
+            mPointerVelocityControl.reset();
+        }
+
+        float x, y;
+        mPointerController->getPosition(&x, &y);
+
+        mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG;
+        mPointerGesture.currentGestureIdBits.clear();
+        mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+        mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+        mPointerGesture.currentGestureProperties[0].clear();
+        mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        mPointerGesture.currentGestureCoords[0].clear();
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+    } else if (currentFingerCount == 0) {
+        // Case 3. No fingers down and button is not pressed. (NEUTRAL)
+        if (mPointerGesture.lastGestureMode != PointerGesture::NEUTRAL) {
+            *outFinishPreviousGesture = true;
+        }
+
+        // Watch for taps coming out of HOVER or TAP_DRAG mode.
+        // Checking for taps after TAP_DRAG allows us to detect double-taps.
+        bool tapped = false;
+        if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER ||
+             mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) &&
+            lastFingerCount == 1) {
+            if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
+                float x, y;
+                mPointerController->getPosition(&x, &y);
+                if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
+                    fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+#if DEBUG_GESTURES
+                    ALOGD("Gestures: TAP");
+#endif
+
+                    mPointerGesture.tapUpTime = when;
+                    getContext()->requestTimeoutAtTime(when +
+                                                       mConfig.pointerGestureTapDragInterval);
+
+                    mPointerGesture.activeGestureId = 0;
+                    mPointerGesture.currentGestureMode = PointerGesture::TAP;
+                    mPointerGesture.currentGestureIdBits.clear();
+                    mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+                    mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+                    mPointerGesture.currentGestureProperties[0].clear();
+                    mPointerGesture.currentGestureProperties[0].id =
+                            mPointerGesture.activeGestureId;
+                    mPointerGesture.currentGestureProperties[0].toolType =
+                            AMOTION_EVENT_TOOL_TYPE_FINGER;
+                    mPointerGesture.currentGestureCoords[0].clear();
+                    mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                                         mPointerGesture.tapX);
+                    mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                                         mPointerGesture.tapY);
+                    mPointerGesture.currentGestureCoords[0]
+                            .setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+                    tapped = true;
+                } else {
+#if DEBUG_GESTURES
+                    ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", x - mPointerGesture.tapX,
+                          y - mPointerGesture.tapY);
+#endif
+                }
+            } else {
+#if DEBUG_GESTURES
+                if (mPointerGesture.tapDownTime != LLONG_MIN) {
+                    ALOGD("Gestures: Not a TAP, %0.3fms since down",
+                          (when - mPointerGesture.tapDownTime) * 0.000001f);
+                } else {
+                    ALOGD("Gestures: Not a TAP, incompatible mode transitions");
+                }
+#endif
+            }
+        }
+
+        mPointerVelocityControl.reset();
+
+        if (!tapped) {
+#if DEBUG_GESTURES
+            ALOGD("Gestures: NEUTRAL");
+#endif
+            mPointerGesture.activeGestureId = -1;
+            mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+            mPointerGesture.currentGestureIdBits.clear();
+        }
+    } else if (currentFingerCount == 1) {
+        // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
+        // The pointer follows the active touch point.
+        // When in HOVER, emit HOVER_MOVE events at the pointer location.
+        // When in TAP_DRAG, emit MOVE events at the pointer location.
+        ALOG_ASSERT(activeTouchId >= 0);
+
+        mPointerGesture.currentGestureMode = PointerGesture::HOVER;
+        if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+            if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+                float x, y;
+                mPointerController->getPosition(&x, &y);
+                if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
+                    fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+                    mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+                } else {
+#if DEBUG_GESTURES
+                    ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
+                          x - mPointerGesture.tapX, y - mPointerGesture.tapY);
+#endif
+                }
+            } else {
+#if DEBUG_GESTURES
+                ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
+                      (when - mPointerGesture.tapUpTime) * 0.000001f);
+#endif
+            }
+        } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) {
+            mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+        }
+
+        float deltaX = 0, deltaY = 0;
+        if (mLastCookedState.fingerIdBits.hasBit(activeTouchId)) {
+            const RawPointerData::Pointer& currentPointer =
+                    mCurrentRawState.rawPointerData.pointerForId(activeTouchId);
+            const RawPointerData::Pointer& lastPointer =
+                    mLastRawState.rawPointerData.pointerForId(activeTouchId);
+            deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale;
+            deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale;
+
+            rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+            mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+            // Move the pointer using a relative motion.
+            // When using spots, the hover or drag will occur at the position of the anchor spot.
+            mPointerController->move(deltaX, deltaY);
+        } else {
+            mPointerVelocityControl.reset();
+        }
+
+        bool down;
+        if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) {
+#if DEBUG_GESTURES
+            ALOGD("Gestures: TAP_DRAG");
+#endif
+            down = true;
+        } else {
+#if DEBUG_GESTURES
+            ALOGD("Gestures: HOVER");
+#endif
+            if (mPointerGesture.lastGestureMode != PointerGesture::HOVER) {
+                *outFinishPreviousGesture = true;
+            }
+            mPointerGesture.activeGestureId = 0;
+            down = false;
+        }
+
+        float x, y;
+        mPointerController->getPosition(&x, &y);
+
+        mPointerGesture.currentGestureIdBits.clear();
+        mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+        mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+        mPointerGesture.currentGestureProperties[0].clear();
+        mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        mPointerGesture.currentGestureCoords[0].clear();
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+                                                             down ? 1.0f : 0.0f);
+
+        if (lastFingerCount == 0 && currentFingerCount != 0) {
+            mPointerGesture.resetTap();
+            mPointerGesture.tapDownTime = when;
+            mPointerGesture.tapX = x;
+            mPointerGesture.tapY = y;
+        }
+    } else {
+        // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+        // We need to provide feedback for each finger that goes down so we cannot wait
+        // for the fingers to move before deciding what to do.
+        //
+        // The ambiguous case is deciding what to do when there are two fingers down but they
+        // have not moved enough to determine whether they are part of a drag or part of a
+        // freeform gesture, or just a press or long-press at the pointer location.
+        //
+        // When there are two fingers we start with the PRESS hypothesis and we generate a
+        // down at the pointer location.
+        //
+        // When the two fingers move enough or when additional fingers are added, we make
+        // a decision to transition into SWIPE or FREEFORM mode accordingly.
+        ALOG_ASSERT(activeTouchId >= 0);
+
+        bool settled = when >=
+                mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval;
+        if (mPointerGesture.lastGestureMode != PointerGesture::PRESS &&
+            mPointerGesture.lastGestureMode != PointerGesture::SWIPE &&
+            mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+            *outFinishPreviousGesture = true;
+        } else if (!settled && currentFingerCount > lastFingerCount) {
+            // Additional pointers have gone down but not yet settled.
+            // Reset the gesture.
+#if DEBUG_GESTURES
+            ALOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+                  "settle time remaining %0.3fms",
+                  (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+                   when) * 0.000001f);
+#endif
+            *outCancelPreviousGesture = true;
+        } else {
+            // Continue previous gesture.
+            mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
+        }
+
+        if (*outFinishPreviousGesture || *outCancelPreviousGesture) {
+            mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+            mPointerGesture.activeGestureId = 0;
+            mPointerGesture.referenceIdBits.clear();
+            mPointerVelocityControl.reset();
+
+            // Use the centroid and pointer location as the reference points for the gesture.
+#if DEBUG_GESTURES
+            ALOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+                  "settle time remaining %0.3fms",
+                  (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+                   when) * 0.000001f);
+#endif
+            mCurrentRawState.rawPointerData
+                    .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
+                                                   &mPointerGesture.referenceTouchY);
+            mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+                                            &mPointerGesture.referenceGestureY);
+        }
+
+        // Clear the reference deltas for fingers not yet included in the reference calculation.
+        for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value &
+                             ~mPointerGesture.referenceIdBits.value);
+             !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            mPointerGesture.referenceDeltas[id].dx = 0;
+            mPointerGesture.referenceDeltas[id].dy = 0;
+        }
+        mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
+
+        // Add delta for all fingers and calculate a common movement delta.
+        float commonDeltaX = 0, commonDeltaY = 0;
+        BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
+                              mCurrentCookedState.fingerIdBits.value);
+        for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
+            bool first = (idBits == commonIdBits);
+            uint32_t id = idBits.clearFirstMarkedBit();
+            const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id);
+            const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id);
+            PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+            delta.dx += cpd.x - lpd.x;
+            delta.dy += cpd.y - lpd.y;
+
+            if (first) {
+                commonDeltaX = delta.dx;
+                commonDeltaY = delta.dy;
+            } else {
+                commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx);
+                commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy);
+            }
+        }
+
+        // Consider transitions from PRESS to SWIPE or MULTITOUCH.
+        if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+            float dist[MAX_POINTER_ID + 1];
+            int32_t distOverThreshold = 0;
+            for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+                uint32_t id = idBits.clearFirstMarkedBit();
+                PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+                dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale);
+                if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
+                    distOverThreshold += 1;
+                }
+            }
+
+            // Only transition when at least two pointers have moved further than
+            // the minimum distance threshold.
+            if (distOverThreshold >= 2) {
+                if (currentFingerCount > 2) {
+                    // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+                    ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+                          currentFingerCount);
+#endif
+                    *outCancelPreviousGesture = true;
+                    mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+                } else {
+                    // There are exactly two pointers.
+                    BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+                    uint32_t id1 = idBits.clearFirstMarkedBit();
+                    uint32_t id2 = idBits.firstMarkedBit();
+                    const RawPointerData::Pointer& p1 =
+                            mCurrentRawState.rawPointerData.pointerForId(id1);
+                    const RawPointerData::Pointer& p2 =
+                            mCurrentRawState.rawPointerData.pointerForId(id2);
+                    float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
+                    if (mutualDistance > mPointerGestureMaxSwipeWidth) {
+                        // There are two pointers but they are too far apart for a SWIPE,
+                        // switch to FREEFORM.
+#if DEBUG_GESTURES
+                        ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+                              mutualDistance, mPointerGestureMaxSwipeWidth);
+#endif
+                        *outCancelPreviousGesture = true;
+                        mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+                    } else {
+                        // There are two pointers.  Wait for both pointers to start moving
+                        // before deciding whether this is a SWIPE or FREEFORM gesture.
+                        float dist1 = dist[id1];
+                        float dist2 = dist[id2];
+                        if (dist1 >= mConfig.pointerGestureMultitouchMinDistance &&
+                            dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
+                            // Calculate the dot product of the displacement vectors.
+                            // When the vectors are oriented in approximately the same direction,
+                            // the angle betweeen them is near zero and the cosine of the angle
+                            // approches 1.0.  Recall that dot(v1, v2) = cos(angle) * mag(v1) *
+                            // mag(v2).
+                            PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
+                            PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
+                            float dx1 = delta1.dx * mPointerXZoomScale;
+                            float dy1 = delta1.dy * mPointerYZoomScale;
+                            float dx2 = delta2.dx * mPointerXZoomScale;
+                            float dy2 = delta2.dy * mPointerYZoomScale;
+                            float dot = dx1 * dx2 + dy1 * dy2;
+                            float cosine = dot / (dist1 * dist2); // denominator always > 0
+                            if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
+                                // Pointers are moving in the same direction.  Switch to SWIPE.
+#if DEBUG_GESTURES
+                                ALOGD("Gestures: PRESS transitioned to SWIPE, "
+                                      "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+                                      "cosine %0.3f >= %0.3f",
+                                      dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+                                      mConfig.pointerGestureMultitouchMinDistance, cosine,
+                                      mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+                                mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+                            } else {
+                                // Pointers are moving in different directions.  Switch to FREEFORM.
+#if DEBUG_GESTURES
+                                ALOGD("Gestures: PRESS transitioned to FREEFORM, "
+                                      "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+                                      "cosine %0.3f < %0.3f",
+                                      dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+                                      mConfig.pointerGestureMultitouchMinDistance, cosine,
+                                      mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+                                *outCancelPreviousGesture = true;
+                                mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+                            }
+                        }
+                    }
+                }
+            }
+        } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+            // Switch from SWIPE to FREEFORM if additional pointers go down.
+            // Cancel previous gesture.
+            if (currentFingerCount > 2) {
+#if DEBUG_GESTURES
+                ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+                      currentFingerCount);
+#endif
+                *outCancelPreviousGesture = true;
+                mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+            }
+        }
+
+        // Move the reference points based on the overall group motion of the fingers
+        // except in PRESS mode while waiting for a transition to occur.
+        if (mPointerGesture.currentGestureMode != PointerGesture::PRESS &&
+            (commonDeltaX || commonDeltaY)) {
+            for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+                uint32_t id = idBits.clearFirstMarkedBit();
+                PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+                delta.dx = 0;
+                delta.dy = 0;
+            }
+
+            mPointerGesture.referenceTouchX += commonDeltaX;
+            mPointerGesture.referenceTouchY += commonDeltaY;
+
+            commonDeltaX *= mPointerXMovementScale;
+            commonDeltaY *= mPointerYMovementScale;
+
+            rotateDelta(mSurfaceOrientation, &commonDeltaX, &commonDeltaY);
+            mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
+
+            mPointerGesture.referenceGestureX += commonDeltaX;
+            mPointerGesture.referenceGestureY += commonDeltaY;
+        }
+
+        // Report gestures.
+        if (mPointerGesture.currentGestureMode == PointerGesture::PRESS ||
+            mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+            // PRESS or SWIPE mode.
+#if DEBUG_GESTURES
+            ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d,"
+                  "activeGestureId=%d, currentTouchPointerCount=%d",
+                  activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+            ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+            mPointerGesture.currentGestureIdBits.clear();
+            mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+            mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+            mPointerGesture.currentGestureProperties[0].clear();
+            mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+            mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            mPointerGesture.currentGestureCoords[0].clear();
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                                 mPointerGesture.referenceGestureX);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                                 mPointerGesture.referenceGestureY);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
+            // FREEFORM mode.
+#if DEBUG_GESTURES
+            ALOGD("Gestures: FREEFORM activeTouchId=%d,"
+                  "activeGestureId=%d, currentTouchPointerCount=%d",
+                  activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+            ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+            mPointerGesture.currentGestureIdBits.clear();
+
+            BitSet32 mappedTouchIdBits;
+            BitSet32 usedGestureIdBits;
+            if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+                // Initially, assign the active gesture id to the active touch point
+                // if there is one.  No other touch id bits are mapped yet.
+                if (!*outCancelPreviousGesture) {
+                    mappedTouchIdBits.markBit(activeTouchId);
+                    usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
+                    mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
+                            mPointerGesture.activeGestureId;
+                } else {
+                    mPointerGesture.activeGestureId = -1;
+                }
+            } else {
+                // Otherwise, assume we mapped all touches from the previous frame.
+                // Reuse all mappings that are still applicable.
+                mappedTouchIdBits.value = mLastCookedState.fingerIdBits.value &
+                        mCurrentCookedState.fingerIdBits.value;
+                usedGestureIdBits = mPointerGesture.lastGestureIdBits;
+
+                // Check whether we need to choose a new active gesture id because the
+                // current went went up.
+                for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value &
+                                            ~mCurrentCookedState.fingerIdBits.value);
+                     !upTouchIdBits.isEmpty();) {
+                    uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
+                    uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
+                    if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
+                        mPointerGesture.activeGestureId = -1;
+                        break;
+                    }
+                }
+            }
+
+#if DEBUG_GESTURES
+            ALOGD("Gestures: FREEFORM follow up "
+                  "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
+                  "activeGestureId=%d",
+                  mappedTouchIdBits.value, usedGestureIdBits.value,
+                  mPointerGesture.activeGestureId);
+#endif
+
+            BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+            for (uint32_t i = 0; i < currentFingerCount; i++) {
+                uint32_t touchId = idBits.clearFirstMarkedBit();
+                uint32_t gestureId;
+                if (!mappedTouchIdBits.hasBit(touchId)) {
+                    gestureId = usedGestureIdBits.markFirstUnmarkedBit();
+                    mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
+#if DEBUG_GESTURES
+                    ALOGD("Gestures: FREEFORM "
+                          "new mapping for touch id %d -> gesture id %d",
+                          touchId, gestureId);
+#endif
+                } else {
+                    gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
+#if DEBUG_GESTURES
+                    ALOGD("Gestures: FREEFORM "
+                          "existing mapping for touch id %d -> gesture id %d",
+                          touchId, gestureId);
+#endif
+                }
+                mPointerGesture.currentGestureIdBits.markBit(gestureId);
+                mPointerGesture.currentGestureIdToIndex[gestureId] = i;
+
+                const RawPointerData::Pointer& pointer =
+                        mCurrentRawState.rawPointerData.pointerForId(touchId);
+                float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
+                float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
+                rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+
+                mPointerGesture.currentGestureProperties[i].clear();
+                mPointerGesture.currentGestureProperties[i].id = gestureId;
+                mPointerGesture.currentGestureProperties[i].toolType =
+                        AMOTION_EVENT_TOOL_TYPE_FINGER;
+                mPointerGesture.currentGestureCoords[i].clear();
+                mPointerGesture.currentGestureCoords[i]
+                        .setAxisValue(AMOTION_EVENT_AXIS_X,
+                                      mPointerGesture.referenceGestureX + deltaX);
+                mPointerGesture.currentGestureCoords[i]
+                        .setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                      mPointerGesture.referenceGestureY + deltaY);
+                mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+                                                                     1.0f);
+            }
+
+            if (mPointerGesture.activeGestureId < 0) {
+                mPointerGesture.activeGestureId =
+                        mPointerGesture.currentGestureIdBits.firstMarkedBit();
+#if DEBUG_GESTURES
+                ALOGD("Gestures: FREEFORM new "
+                      "activeGestureId=%d",
+                      mPointerGesture.activeGestureId);
+#endif
+            }
+        }
+    }
+
+    mPointerController->setButtonState(mCurrentRawState.buttonState);
+
+#if DEBUG_GESTURES
+    ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
+          "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+          "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+          toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
+          mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+          mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+    for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+        const PointerProperties& properties = mPointerGesture.currentGestureProperties[index];
+        const PointerCoords& coords = mPointerGesture.currentGestureCoords[index];
+        ALOGD("  currentGesture[%d]: index=%d, toolType=%d, "
+              "x=%0.3f, y=%0.3f, pressure=%0.3f",
+              id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+              coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+              coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+    }
+    for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        uint32_t index = mPointerGesture.lastGestureIdToIndex[id];
+        const PointerProperties& properties = mPointerGesture.lastGestureProperties[index];
+        const PointerCoords& coords = mPointerGesture.lastGestureCoords[index];
+        ALOGD("  lastGesture[%d]: index=%d, toolType=%d, "
+              "x=%0.3f, y=%0.3f, pressure=%0.3f",
+              id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+              coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+              coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+    }
+#endif
+    return true;
+}
+
+void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) {
+    mPointerSimple.currentCoords.clear();
+    mPointerSimple.currentProperties.clear();
+
+    bool down, hovering;
+    if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
+        uint32_t id = mCurrentCookedState.stylusIdBits.firstMarkedBit();
+        uint32_t index = mCurrentCookedState.cookedPointerData.idToIndex[id];
+        float x = mCurrentCookedState.cookedPointerData.pointerCoords[index].getX();
+        float y = mCurrentCookedState.cookedPointerData.pointerCoords[index].getY();
+        mPointerController->setPosition(x, y);
+
+        hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id);
+        down = !hovering;
+
+        mPointerController->getPosition(&x, &y);
+        mPointerSimple.currentCoords.copyFrom(
+                mCurrentCookedState.cookedPointerData.pointerCoords[index]);
+        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        mPointerSimple.currentProperties.id = 0;
+        mPointerSimple.currentProperties.toolType =
+                mCurrentCookedState.cookedPointerData.pointerProperties[index].toolType;
+    } else {
+        down = false;
+        hovering = false;
+    }
+
+    dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerStylus(nsecs_t when, uint32_t policyFlags) {
+    abortPointerSimple(when, policyFlags);
+}
+
+void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) {
+    mPointerSimple.currentCoords.clear();
+    mPointerSimple.currentProperties.clear();
+
+    bool down, hovering;
+    if (!mCurrentCookedState.mouseIdBits.isEmpty()) {
+        uint32_t id = mCurrentCookedState.mouseIdBits.firstMarkedBit();
+        uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id];
+        float deltaX = 0, deltaY = 0;
+        if (mLastCookedState.mouseIdBits.hasBit(id)) {
+            uint32_t lastIndex = mCurrentRawState.rawPointerData.idToIndex[id];
+            deltaX = (mCurrentRawState.rawPointerData.pointers[currentIndex].x -
+                      mLastRawState.rawPointerData.pointers[lastIndex].x) *
+                    mPointerXMovementScale;
+            deltaY = (mCurrentRawState.rawPointerData.pointers[currentIndex].y -
+                      mLastRawState.rawPointerData.pointers[lastIndex].y) *
+                    mPointerYMovementScale;
+
+            rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+            mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+            mPointerController->move(deltaX, deltaY);
+        } else {
+            mPointerVelocityControl.reset();
+        }
+
+        down = isPointerDown(mCurrentRawState.buttonState);
+        hovering = !down;
+
+        float x, y;
+        mPointerController->getPosition(&x, &y);
+        mPointerSimple.currentCoords.copyFrom(
+                mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]);
+        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+                                                  hovering ? 0.0f : 1.0f);
+        mPointerSimple.currentProperties.id = 0;
+        mPointerSimple.currentProperties.toolType =
+                mCurrentCookedState.cookedPointerData.pointerProperties[currentIndex].toolType;
+    } else {
+        mPointerVelocityControl.reset();
+
+        down = false;
+        hovering = false;
+    }
+
+    dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerMouse(nsecs_t when, uint32_t policyFlags) {
+    abortPointerSimple(when, policyFlags);
+
+    mPointerVelocityControl.reset();
+}
+
+void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, bool down,
+                                             bool hovering) {
+    int32_t metaState = getContext()->getGlobalMetaState();
+    int32_t displayId = mViewport.displayId;
+
+    if (down || hovering) {
+        mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+        mPointerController->clearSpots();
+        mPointerController->setButtonState(mCurrentRawState.buttonState);
+        mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+    } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) {
+        mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+    }
+    displayId = mPointerController->getDisplayId();
+
+    float xCursorPosition;
+    float yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+    if (mPointerSimple.down && !down) {
+        mPointerSimple.down = false;
+
+        // Send up.
+        NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_UP, 0, 0, metaState,
+                              mLastRawState.buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
+                              &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
+                              xCursorPosition, yCursorPosition, mPointerSimple.downTime,
+                              /* videoFrames */ {});
+        getListener()->notifyMotion(&args);
+    }
+
+    if (mPointerSimple.hovering && !hovering) {
+        mPointerSimple.hovering = false;
+
+        // Send hover exit.
+        NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0,
+                              metaState, mLastRawState.buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
+                              &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
+                              xCursorPosition, yCursorPosition, mPointerSimple.downTime,
+                              /* videoFrames */ {});
+        getListener()->notifyMotion(&args);
+    }
+
+    if (down) {
+        if (!mPointerSimple.down) {
+            mPointerSimple.down = true;
+            mPointerSimple.downTime = when;
+
+            // Send down.
+            NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                                  displayId, policyFlags, AMOTION_EVENT_ACTION_DOWN, 0, 0,
+                                  metaState, mCurrentRawState.buttonState,
+                                  MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+                                  &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+                                  mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
+                                  yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
+            getListener()->notifyMotion(&args);
+        }
+
+        // Send move.
+        NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
+                              mCurrentRawState.buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
+                              &mPointerSimple.currentCoords, mOrientedXPrecision,
+                              mOrientedYPrecision, xCursorPosition, yCursorPosition,
+                              mPointerSimple.downTime, /* videoFrames */ {});
+        getListener()->notifyMotion(&args);
+    }
+
+    if (hovering) {
+        if (!mPointerSimple.hovering) {
+            mPointerSimple.hovering = true;
+
+            // Send hover enter.
+            NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                                  displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0,
+                                  metaState, mCurrentRawState.buttonState,
+                                  MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+                                  &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+                                  mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
+                                  yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
+            getListener()->notifyMotion(&args);
+        }
+
+        // Send hover move.
+        NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
+                              metaState, mCurrentRawState.buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
+                              &mPointerSimple.currentCoords, mOrientedXPrecision,
+                              mOrientedYPrecision, xCursorPosition, yCursorPosition,
+                              mPointerSimple.downTime, /* videoFrames */ {});
+        getListener()->notifyMotion(&args);
+    }
+
+    if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) {
+        float vscroll = mCurrentRawState.rawVScroll;
+        float hscroll = mCurrentRawState.rawHScroll;
+        mWheelYVelocityControl.move(when, nullptr, &vscroll);
+        mWheelXVelocityControl.move(when, &hscroll, nullptr);
+
+        // Send scroll.
+        PointerCoords pointerCoords;
+        pointerCoords.copyFrom(mPointerSimple.currentCoords);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+        NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
+                              mCurrentRawState.buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
+                              &pointerCoords, mOrientedXPrecision, mOrientedYPrecision,
+                              xCursorPosition, yCursorPosition, mPointerSimple.downTime,
+                              /* videoFrames */ {});
+        getListener()->notifyMotion(&args);
+    }
+
+    // Save state.
+    if (down || hovering) {
+        mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
+        mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+    } else {
+        mPointerSimple.reset();
+    }
+}
+
+void TouchInputMapper::abortPointerSimple(nsecs_t when, uint32_t policyFlags) {
+    mPointerSimple.currentCoords.clear();
+    mPointerSimple.currentProperties.clear();
+
+    dispatchPointerSimple(when, policyFlags, false, false);
+}
+
+void TouchInputMapper::dispatchMotion(nsecs_t when, 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 PointerProperties* properties,
+                                      const PointerCoords* coords, const uint32_t* idToIndex,
+                                      BitSet32 idBits, int32_t changedId, float xPrecision,
+                                      float yPrecision, nsecs_t downTime) {
+    PointerCoords pointerCoords[MAX_POINTERS];
+    PointerProperties pointerProperties[MAX_POINTERS];
+    uint32_t pointerCount = 0;
+    while (!idBits.isEmpty()) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        uint32_t index = idToIndex[id];
+        pointerProperties[pointerCount].copyFrom(properties[index]);
+        pointerCoords[pointerCount].copyFrom(coords[index]);
+
+        if (changedId >= 0 && id == uint32_t(changedId)) {
+            action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+        }
+
+        pointerCount += 1;
+    }
+
+    ALOG_ASSERT(pointerCount != 0);
+
+    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) {
+            action = AMOTION_EVENT_ACTION_DOWN;
+        } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
+            action = AMOTION_EVENT_ACTION_UP;
+        } else {
+            // Can't happen.
+            ALOG_ASSERT(false);
+        }
+    }
+    float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+    float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+    if (mDeviceMode == DEVICE_MODE_POINTER) {
+        mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    }
+    const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
+    const int32_t deviceId = getDeviceId();
+    std::vector<TouchVideoFrame> frames = mDevice->getEventHub()->getVideoFrames(deviceId);
+    std::for_each(frames.begin(), frames.end(),
+                  [this](TouchVideoFrame& frame) { frame.rotate(this->mSurfaceOrientation); });
+    NotifyMotionArgs args(mContext->getNextSequenceNum(), when, deviceId, source, displayId,
+                          policyFlags, action, actionButton, flags, metaState, buttonState,
+                          MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
+                          pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
+                          downTime, std::move(frames));
+    getListener()->notifyMotion(&args);
+}
+
+bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties,
+                                           const PointerCoords* inCoords,
+                                           const uint32_t* inIdToIndex,
+                                           PointerProperties* outProperties,
+                                           PointerCoords* outCoords, const uint32_t* outIdToIndex,
+                                           BitSet32 idBits) const {
+    bool changed = false;
+    while (!idBits.isEmpty()) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        uint32_t inIndex = inIdToIndex[id];
+        uint32_t outIndex = outIdToIndex[id];
+
+        const PointerProperties& curInProperties = inProperties[inIndex];
+        const PointerCoords& curInCoords = inCoords[inIndex];
+        PointerProperties& curOutProperties = outProperties[outIndex];
+        PointerCoords& curOutCoords = outCoords[outIndex];
+
+        if (curInProperties != curOutProperties) {
+            curOutProperties.copyFrom(curInProperties);
+            changed = true;
+        }
+
+        if (curInCoords != curOutCoords) {
+            curOutCoords.copyFrom(curInCoords);
+            changed = true;
+        }
+    }
+    return changed;
+}
+
+void TouchInputMapper::fadePointer() {
+    if (mPointerController != nullptr) {
+        mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+    }
+}
+
+void TouchInputMapper::cancelTouch(nsecs_t when) {
+    abortPointerUsage(when, 0 /*policyFlags*/);
+    abortTouches(when, 0 /* policyFlags*/);
+}
+
+bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) {
+    const float scaledX = x * mXScale;
+    const float scaledY = y * mYScale;
+    return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
+            scaledX >= mPhysicalLeft && scaledX <= mPhysicalLeft + mPhysicalWidth &&
+            y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
+            scaledY >= mPhysicalTop && scaledY <= mPhysicalTop + mPhysicalHeight;
+}
+
+const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) {
+    for (const VirtualKey& virtualKey : mVirtualKeys) {
+#if DEBUG_VIRTUAL_KEYS
+        ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+              "left=%d, top=%d, right=%d, bottom=%d",
+              x, y, virtualKey.keyCode, virtualKey.scanCode, virtualKey.hitLeft, virtualKey.hitTop,
+              virtualKey.hitRight, virtualKey.hitBottom);
+#endif
+
+        if (virtualKey.isHit(x, y)) {
+            return &virtualKey;
+        }
+    }
+
+    return nullptr;
+}
+
+void TouchInputMapper::assignPointerIds(const RawState* last, RawState* current) {
+    uint32_t currentPointerCount = current->rawPointerData.pointerCount;
+    uint32_t lastPointerCount = last->rawPointerData.pointerCount;
+
+    current->rawPointerData.clearIdBits();
+
+    if (currentPointerCount == 0) {
+        // No pointers to assign.
+        return;
+    }
+
+    if (lastPointerCount == 0) {
+        // All pointers are new.
+        for (uint32_t i = 0; i < currentPointerCount; i++) {
+            uint32_t id = i;
+            current->rawPointerData.pointers[i].id = id;
+            current->rawPointerData.idToIndex[id] = i;
+            current->rawPointerData.markIdBit(id, current->rawPointerData.isHovering(i));
+        }
+        return;
+    }
+
+    if (currentPointerCount == 1 && lastPointerCount == 1 &&
+        current->rawPointerData.pointers[0].toolType == last->rawPointerData.pointers[0].toolType) {
+        // Only one pointer and no change in count so it must have the same id as before.
+        uint32_t id = last->rawPointerData.pointers[0].id;
+        current->rawPointerData.pointers[0].id = id;
+        current->rawPointerData.idToIndex[id] = 0;
+        current->rawPointerData.markIdBit(id, current->rawPointerData.isHovering(0));
+        return;
+    }
+
+    // General case.
+    // We build a heap of squared euclidean distances between current and last pointers
+    // associated with the current and last pointer indices.  Then, we find the best
+    // match (by distance) for each current pointer.
+    // The pointers must have the same tool type but it is possible for them to
+    // transition from hovering to touching or vice-versa while retaining the same id.
+    PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
+
+    uint32_t heapSize = 0;
+    for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
+         currentPointerIndex++) {
+        for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
+             lastPointerIndex++) {
+            const RawPointerData::Pointer& currentPointer =
+                    current->rawPointerData.pointers[currentPointerIndex];
+            const RawPointerData::Pointer& lastPointer =
+                    last->rawPointerData.pointers[lastPointerIndex];
+            if (currentPointer.toolType == lastPointer.toolType) {
+                int64_t deltaX = currentPointer.x - lastPointer.x;
+                int64_t deltaY = currentPointer.y - lastPointer.y;
+
+                uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+                // Insert new element into the heap (sift up).
+                heap[heapSize].currentPointerIndex = currentPointerIndex;
+                heap[heapSize].lastPointerIndex = lastPointerIndex;
+                heap[heapSize].distance = distance;
+                heapSize += 1;
+            }
+        }
+    }
+
+    // Heapify
+    for (uint32_t startIndex = heapSize / 2; startIndex != 0;) {
+        startIndex -= 1;
+        for (uint32_t parentIndex = startIndex;;) {
+            uint32_t childIndex = parentIndex * 2 + 1;
+            if (childIndex >= heapSize) {
+                break;
+            }
+
+            if (childIndex + 1 < heapSize &&
+                heap[childIndex + 1].distance < heap[childIndex].distance) {
+                childIndex += 1;
+            }
+
+            if (heap[parentIndex].distance <= heap[childIndex].distance) {
+                break;
+            }
+
+            swap(heap[parentIndex], heap[childIndex]);
+            parentIndex = childIndex;
+        }
+    }
+
+#if DEBUG_POINTER_ASSIGNMENT
+    ALOGD("assignPointerIds - initial distance min-heap: size=%d", heapSize);
+    for (size_t i = 0; i < heapSize; i++) {
+        ALOGD("  heap[%zu]: cur=%" PRIu32 ", last=%" PRIu32 ", distance=%" PRIu64, i,
+              heap[i].currentPointerIndex, heap[i].lastPointerIndex, heap[i].distance);
+    }
+#endif
+
+    // Pull matches out by increasing order of distance.
+    // To avoid reassigning pointers that have already been matched, the loop keeps track
+    // of which last and current pointers have been matched using the matchedXXXBits variables.
+    // It also tracks the used pointer id bits.
+    BitSet32 matchedLastBits(0);
+    BitSet32 matchedCurrentBits(0);
+    BitSet32 usedIdBits(0);
+    bool first = true;
+    for (uint32_t i = min(currentPointerCount, lastPointerCount); heapSize > 0 && i > 0; i--) {
+        while (heapSize > 0) {
+            if (first) {
+                // The first time through the loop, we just consume the root element of
+                // the heap (the one with smallest distance).
+                first = false;
+            } else {
+                // Previous iterations consumed the root element of the heap.
+                // Pop root element off of the heap (sift down).
+                heap[0] = heap[heapSize];
+                for (uint32_t parentIndex = 0;;) {
+                    uint32_t childIndex = parentIndex * 2 + 1;
+                    if (childIndex >= heapSize) {
+                        break;
+                    }
+
+                    if (childIndex + 1 < heapSize &&
+                        heap[childIndex + 1].distance < heap[childIndex].distance) {
+                        childIndex += 1;
+                    }
+
+                    if (heap[parentIndex].distance <= heap[childIndex].distance) {
+                        break;
+                    }
+
+                    swap(heap[parentIndex], heap[childIndex]);
+                    parentIndex = childIndex;
+                }
+
+#if DEBUG_POINTER_ASSIGNMENT
+                ALOGD("assignPointerIds - reduced distance min-heap: size=%d", heapSize);
+                for (size_t i = 0; i < heapSize; i++) {
+                    ALOGD("  heap[%zu]: cur=%" PRIu32 ", last=%" PRIu32 ", distance=%" PRIu64, i,
+                          heap[i].currentPointerIndex, heap[i].lastPointerIndex, heap[i].distance);
+                }
+#endif
+            }
+
+            heapSize -= 1;
+
+            uint32_t currentPointerIndex = heap[0].currentPointerIndex;
+            if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
+
+            uint32_t lastPointerIndex = heap[0].lastPointerIndex;
+            if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
+
+            matchedCurrentBits.markBit(currentPointerIndex);
+            matchedLastBits.markBit(lastPointerIndex);
+
+            uint32_t id = last->rawPointerData.pointers[lastPointerIndex].id;
+            current->rawPointerData.pointers[currentPointerIndex].id = id;
+            current->rawPointerData.idToIndex[id] = currentPointerIndex;
+            current->rawPointerData.markIdBit(id,
+                                              current->rawPointerData.isHovering(
+                                                      currentPointerIndex));
+            usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+            ALOGD("assignPointerIds - matched: cur=%" PRIu32 ", last=%" PRIu32 ", id=%" PRIu32
+                  ", distance=%" PRIu64,
+                  lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
+            break;
+        }
+    }
+
+    // Assign fresh ids to pointers that were not matched in the process.
+    for (uint32_t i = currentPointerCount - matchedCurrentBits.count(); i != 0; i--) {
+        uint32_t currentPointerIndex = matchedCurrentBits.markFirstUnmarkedBit();
+        uint32_t id = usedIdBits.markFirstUnmarkedBit();
+
+        current->rawPointerData.pointers[currentPointerIndex].id = id;
+        current->rawPointerData.idToIndex[id] = currentPointerIndex;
+        current->rawPointerData.markIdBit(id,
+                                          current->rawPointerData.isHovering(currentPointerIndex));
+
+#if DEBUG_POINTER_ASSIGNMENT
+        ALOGD("assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex, id);
+#endif
+    }
+}
+
+int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    if (mCurrentVirtualKey.down && mCurrentVirtualKey.keyCode == keyCode) {
+        return AKEY_STATE_VIRTUAL;
+    }
+
+    for (const VirtualKey& virtualKey : mVirtualKeys) {
+        if (virtualKey.keyCode == keyCode) {
+            return AKEY_STATE_UP;
+        }
+    }
+
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    if (mCurrentVirtualKey.down && mCurrentVirtualKey.scanCode == scanCode) {
+        return AKEY_STATE_VIRTUAL;
+    }
+
+    for (const VirtualKey& virtualKey : mVirtualKeys) {
+        if (virtualKey.scanCode == scanCode) {
+            return AKEY_STATE_UP;
+        }
+    }
+
+    return AKEY_STATE_UNKNOWN;
+}
+
+bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+                                             const int32_t* keyCodes, uint8_t* outFlags) {
+    for (const VirtualKey& virtualKey : mVirtualKeys) {
+        for (size_t i = 0; i < numCodes; i++) {
+            if (virtualKey.keyCode == keyCodes[i]) {
+                outFlags[i] = 1;
+            }
+        }
+    }
+
+    return true;
+}
+
+std::optional<int32_t> TouchInputMapper::getAssociatedDisplayId() {
+    if (mParameters.hasAssociatedDisplay) {
+        if (mDeviceMode == DEVICE_MODE_POINTER) {
+            return std::make_optional(mPointerController->getDisplayId());
+        } else {
+            return std::make_optional(mViewport.displayId);
+        }
+    }
+    return std::nullopt;
+}
+
+// --- SingleTouchInputMapper ---
+
+SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) : TouchInputMapper(device) {}
+
+SingleTouchInputMapper::~SingleTouchInputMapper() {}
+
+void SingleTouchInputMapper::reset(nsecs_t when) {
+    mSingleTouchMotionAccumulator.reset(getDevice());
+
+    TouchInputMapper::reset(when);
+}
+
+void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+    TouchInputMapper::process(rawEvent);
+
+    mSingleTouchMotionAccumulator.process(rawEvent);
+}
+
+void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
+    if (mTouchButtonAccumulator.isToolActive()) {
+        outState->rawPointerData.pointerCount = 1;
+        outState->rawPointerData.idToIndex[0] = 0;
+
+        bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_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 == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+            outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        }
+        outPointer.isHovering = isHovering;
+    }
+}
+
+void SingleTouchInputMapper::configureRawPointerAxes() {
+    TouchInputMapper::configureRawPointerAxes();
+
+    getAbsoluteAxisInfo(ABS_X, &mRawPointerAxes.x);
+    getAbsoluteAxisInfo(ABS_Y, &mRawPointerAxes.y);
+    getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPointerAxes.pressure);
+    getAbsoluteAxisInfo(ABS_TOOL_WIDTH, &mRawPointerAxes.toolMajor);
+    getAbsoluteAxisInfo(ABS_DISTANCE, &mRawPointerAxes.distance);
+    getAbsoluteAxisInfo(ABS_TILT_X, &mRawPointerAxes.tiltX);
+    getAbsoluteAxisInfo(ABS_TILT_Y, &mRawPointerAxes.tiltY);
+}
+
+bool SingleTouchInputMapper::hasStylus() const {
+    return mTouchButtonAccumulator.hasStylus();
+}
+
+// --- MultiTouchInputMapper ---
+
+MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) : TouchInputMapper(device) {}
+
+MultiTouchInputMapper::~MultiTouchInputMapper() {}
+
+void MultiTouchInputMapper::reset(nsecs_t when) {
+    mMultiTouchMotionAccumulator.reset(getDevice());
+
+    mPointerIdBits.clear();
+
+    TouchInputMapper::reset(when);
+}
+
+void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+    TouchInputMapper::process(rawEvent);
+
+    mMultiTouchMotionAccumulator.process(rawEvent);
+}
+
+void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
+    size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
+    size_t outCount = 0;
+    BitSet32 newPointerIdBits;
+    mHavePointerIds = true;
+
+    for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
+        const MultiTouchMotionAccumulator::Slot* inSlot =
+                mMultiTouchMotionAccumulator.getSlot(inIndex);
+        if (!inSlot->isInUse()) {
+            continue;
+        }
+
+        if (outCount >= MAX_POINTERS) {
+#if DEBUG_POINTERS
+            ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+                  "ignoring the rest.",
+                  getDeviceName().c_str(), MAX_POINTERS);
+#endif
+            break; // too many fingers!
+        }
+
+        RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount];
+        outPointer.x = inSlot->getX();
+        outPointer.y = inSlot->getY();
+        outPointer.pressure = inSlot->getPressure();
+        outPointer.touchMajor = inSlot->getTouchMajor();
+        outPointer.touchMinor = inSlot->getTouchMinor();
+        outPointer.toolMajor = inSlot->getToolMajor();
+        outPointer.toolMinor = inSlot->getToolMinor();
+        outPointer.orientation = inSlot->getOrientation();
+        outPointer.distance = inSlot->getDistance();
+        outPointer.tiltX = 0;
+        outPointer.tiltY = 0;
+
+        outPointer.toolType = inSlot->getToolType();
+        if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+            outPointer.toolType = mTouchButtonAccumulator.getToolType();
+            if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+                outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            }
+        }
+
+        bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE &&
+                (mTouchButtonAccumulator.isHovering() ||
+                 (mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0));
+        outPointer.isHovering = isHovering;
+
+        // Assign pointer id using tracking id if available.
+        if (mHavePointerIds) {
+            int32_t trackingId = inSlot->getTrackingId();
+            int32_t id = -1;
+            if (trackingId >= 0) {
+                for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) {
+                    uint32_t n = idBits.clearFirstMarkedBit();
+                    if (mPointerTrackingIdMap[n] == trackingId) {
+                        id = n;
+                    }
+                }
+
+                if (id < 0 && !mPointerIdBits.isFull()) {
+                    id = mPointerIdBits.markFirstUnmarkedBit();
+                    mPointerTrackingIdMap[id] = trackingId;
+                }
+            }
+            if (id < 0) {
+                mHavePointerIds = false;
+                outState->rawPointerData.clearIdBits();
+                newPointerIdBits.clear();
+            } else {
+                outPointer.id = id;
+                outState->rawPointerData.idToIndex[id] = outCount;
+                outState->rawPointerData.markIdBit(id, isHovering);
+                newPointerIdBits.markBit(id);
+            }
+        }
+        outCount += 1;
+    }
+
+    outState->rawPointerData.pointerCount = outCount;
+    mPointerIdBits = newPointerIdBits;
+
+    mMultiTouchMotionAccumulator.finishSync();
+}
+
+void MultiTouchInputMapper::configureRawPointerAxes() {
+    TouchInputMapper::configureRawPointerAxes();
+
+    getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
+    getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
+    getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
+    getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
+    getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
+    getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
+    getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
+    getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
+    getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
+    getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
+    getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
+
+    if (mRawPointerAxes.trackingId.valid && mRawPointerAxes.slot.valid &&
+        mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
+        size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
+        if (slotCount > MAX_SLOTS) {
+            ALOGW("MultiTouch Device %s reported %zu slots but the framework "
+                  "only supports a maximum of %zu slots at this time.",
+                  getDeviceName().c_str(), slotCount, MAX_SLOTS);
+            slotCount = MAX_SLOTS;
+        }
+        mMultiTouchMotionAccumulator.configure(getDevice(), slotCount, true /*usingSlotsProtocol*/);
+    } else {
+        mMultiTouchMotionAccumulator.configure(getDevice(), MAX_POINTERS,
+                                               false /*usingSlotsProtocol*/);
+    }
+}
+
+bool MultiTouchInputMapper::hasStylus() const {
+    return mMultiTouchMotionAccumulator.hasStylus() || mTouchButtonAccumulator.hasStylus();
+}
+
+// --- ExternalStylusInputMapper
+
+ExternalStylusInputMapper::ExternalStylusInputMapper(InputDevice* device) : InputMapper(device) {}
+
+uint32_t ExternalStylusInputMapper::getSources() {
+    return AINPUT_SOURCE_STYLUS;
+}
+
+void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+    info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f, 0.0f,
+                         0.0f);
+}
+
+void ExternalStylusInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "External Stylus Input Mapper:\n";
+    dump += INDENT3 "Raw Stylus Axes:\n";
+    dumpRawAbsoluteAxisInfo(dump, mRawPressureAxis, "Pressure");
+    dump += INDENT3 "Stylus State:\n";
+    dumpStylusState(dump, mStylusState);
+}
+
+void ExternalStylusInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                                          uint32_t changes) {
+    getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis);
+    mTouchButtonAccumulator.configure(getDevice());
+}
+
+void ExternalStylusInputMapper::reset(nsecs_t when) {
+    InputDevice* device = getDevice();
+    mSingleTouchMotionAccumulator.reset(device);
+    mTouchButtonAccumulator.reset(device);
+    InputMapper::reset(when);
+}
+
+void ExternalStylusInputMapper::process(const RawEvent* rawEvent) {
+    mSingleTouchMotionAccumulator.process(rawEvent);
+    mTouchButtonAccumulator.process(rawEvent);
+
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+        sync(rawEvent->when);
+    }
+}
+
+void ExternalStylusInputMapper::sync(nsecs_t when) {
+    mStylusState.clear();
+
+    mStylusState.when = when;
+
+    mStylusState.toolType = mTouchButtonAccumulator.getToolType();
+    if (mStylusState.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+        mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    }
+
+    int32_t pressure = mSingleTouchMotionAccumulator.getAbsolutePressure();
+    if (mRawPressureAxis.valid) {
+        mStylusState.pressure = float(pressure) / mRawPressureAxis.maxValue;
+    } else if (mTouchButtonAccumulator.isToolActive()) {
+        mStylusState.pressure = 1.0f;
+    } else {
+        mStylusState.pressure = 0.0f;
+    }
+
+    mStylusState.buttons = mTouchButtonAccumulator.getButtonState();
+
+    mContext->dispatchExternalStylusState(mStylusState);
+}
+
+// --- JoystickInputMapper ---
+
+JoystickInputMapper::JoystickInputMapper(InputDevice* device) : InputMapper(device) {}
+
+JoystickInputMapper::~JoystickInputMapper() {}
+
+uint32_t JoystickInputMapper::getSources() {
+    return AINPUT_SOURCE_JOYSTICK;
+}
+
+void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    for (size_t i = 0; i < mAxes.size(); i++) {
+        const Axis& axis = mAxes.valueAt(i);
+        addMotionRange(axis.axisInfo.axis, axis, info);
+
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            addMotionRange(axis.axisInfo.highAxis, axis, info);
+        }
+    }
+}
+
+void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info) {
+    info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, axis.fuzz,
+                         axis.resolution);
+    /* In order to ease the transition for developers from using the old axes
+     * to the newer, more semantically correct axes, we'll continue to register
+     * the old axes as duplicates of their corresponding new ones.  */
+    int32_t compatAxis = getCompatAxis(axisId);
+    if (compatAxis >= 0) {
+        info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat,
+                             axis.fuzz, axis.resolution);
+    }
+}
+
+/* A mapping from axes the joystick actually has to the axes that should be
+ * artificially created for compatibility purposes.
+ * Returns -1 if no compatibility axis is needed. */
+int32_t JoystickInputMapper::getCompatAxis(int32_t axis) {
+    switch (axis) {
+        case AMOTION_EVENT_AXIS_LTRIGGER:
+            return AMOTION_EVENT_AXIS_BRAKE;
+        case AMOTION_EVENT_AXIS_RTRIGGER:
+            return AMOTION_EVENT_AXIS_GAS;
+    }
+    return -1;
+}
+
+void JoystickInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "Joystick Input Mapper:\n";
+
+    dump += INDENT3 "Axes:\n";
+    size_t numAxes = mAxes.size();
+    for (size_t i = 0; i < numAxes; i++) {
+        const Axis& axis = mAxes.valueAt(i);
+        const char* label = getAxisLabel(axis.axisInfo.axis);
+        if (label) {
+            dump += StringPrintf(INDENT4 "%s", label);
+        } else {
+            dump += StringPrintf(INDENT4 "%d", axis.axisInfo.axis);
+        }
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            label = getAxisLabel(axis.axisInfo.highAxis);
+            if (label) {
+                dump += StringPrintf(" / %s (split at %d)", label, axis.axisInfo.splitValue);
+            } else {
+                dump += StringPrintf(" / %d (split at %d)", axis.axisInfo.highAxis,
+                                     axis.axisInfo.splitValue);
+            }
+        } else if (axis.axisInfo.mode == AxisInfo::MODE_INVERT) {
+            dump += " (invert)";
+        }
+
+        dump += StringPrintf(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f, resolution=%0.5f\n",
+                             axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+        dump += StringPrintf(INDENT4 "  scale=%0.5f, offset=%0.5f, "
+                                     "highScale=%0.5f, highOffset=%0.5f\n",
+                             axis.scale, axis.offset, axis.highScale, axis.highOffset);
+        dump += StringPrintf(INDENT4 "  rawAxis=%d, rawMin=%d, rawMax=%d, "
+                                     "rawFlat=%d, rawFuzz=%d, rawResolution=%d\n",
+                             mAxes.keyAt(i), axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue,
+                             axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz,
+                             axis.rawAxisInfo.resolution);
+    }
+}
+
+void JoystickInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                                    uint32_t changes) {
+    InputMapper::configure(when, config, changes);
+
+    if (!changes) { // first time only
+        // Collect all axes.
+        for (int32_t abs = 0; abs <= ABS_MAX; abs++) {
+            if (!(getAbsAxisUsage(abs, getDevice()->getClasses()) & INPUT_DEVICE_CLASS_JOYSTICK)) {
+                continue; // axis must be claimed by a different device
+            }
+
+            RawAbsoluteAxisInfo rawAxisInfo;
+            getAbsoluteAxisInfo(abs, &rawAxisInfo);
+            if (rawAxisInfo.valid) {
+                // Map axis.
+                AxisInfo axisInfo;
+                bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisInfo);
+                if (!explicitlyMapped) {
+                    // Axis is not explicitly mapped, will choose a generic axis later.
+                    axisInfo.mode = AxisInfo::MODE_NORMAL;
+                    axisInfo.axis = -1;
+                }
+
+                // Apply flat override.
+                int32_t rawFlat =
+                        axisInfo.flatOverride < 0 ? rawAxisInfo.flat : axisInfo.flatOverride;
+
+                // Calculate scaling factors and limits.
+                Axis axis;
+                if (axisInfo.mode == AxisInfo::MODE_SPLIT) {
+                    float scale = 1.0f / (axisInfo.splitValue - rawAxisInfo.minValue);
+                    float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue);
+                    axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, scale, 0.0f, highScale,
+                                    0.0f, 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+                                    rawAxisInfo.resolution * scale);
+                } else if (isCenteredAxis(axisInfo.axis)) {
+                    float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+                    float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale;
+                    axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, scale, offset, scale,
+                                    offset, -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+                                    rawAxisInfo.resolution * scale);
+                } else {
+                    float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+                    axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, scale, 0.0f, scale,
+                                    0.0f, 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+                                    rawAxisInfo.resolution * scale);
+                }
+
+                // To eliminate noise while the joystick is at rest, filter out small variations
+                // in axis values up front.
+                axis.filter = axis.fuzz ? axis.fuzz : axis.flat * 0.25f;
+
+                mAxes.add(abs, axis);
+            }
+        }
+
+        // If there are too many axes, start dropping them.
+        // Prefer to keep explicitly mapped axes.
+        if (mAxes.size() > PointerCoords::MAX_AXES) {
+            ALOGI("Joystick '%s' has %zu axes but the framework only supports a maximum of %d.",
+                  getDeviceName().c_str(), mAxes.size(), PointerCoords::MAX_AXES);
+            pruneAxes(true);
+            pruneAxes(false);
+        }
+
+        // Assign generic axis ids to remaining axes.
+        int32_t nextGenericAxisId = AMOTION_EVENT_AXIS_GENERIC_1;
+        size_t numAxes = mAxes.size();
+        for (size_t i = 0; i < numAxes; i++) {
+            Axis& axis = mAxes.editValueAt(i);
+            if (axis.axisInfo.axis < 0) {
+                while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16 &&
+                       haveAxis(nextGenericAxisId)) {
+                    nextGenericAxisId += 1;
+                }
+
+                if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) {
+                    axis.axisInfo.axis = nextGenericAxisId;
+                    nextGenericAxisId += 1;
+                } else {
+                    ALOGI("Ignoring joystick '%s' axis %d because all of the generic axis ids "
+                          "have already been assigned to other axes.",
+                          getDeviceName().c_str(), mAxes.keyAt(i));
+                    mAxes.removeItemsAt(i--);
+                    numAxes -= 1;
+                }
+            }
+        }
+    }
+}
+
+bool JoystickInputMapper::haveAxis(int32_t axisId) {
+    size_t numAxes = mAxes.size();
+    for (size_t i = 0; i < numAxes; i++) {
+        const Axis& axis = mAxes.valueAt(i);
+        if (axis.axisInfo.axis == axisId ||
+            (axis.axisInfo.mode == AxisInfo::MODE_SPLIT && axis.axisInfo.highAxis == axisId)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void JoystickInputMapper::pruneAxes(bool ignoreExplicitlyMappedAxes) {
+    size_t i = mAxes.size();
+    while (mAxes.size() > PointerCoords::MAX_AXES && i-- > 0) {
+        if (ignoreExplicitlyMappedAxes && mAxes.valueAt(i).explicitlyMapped) {
+            continue;
+        }
+        ALOGI("Discarding joystick '%s' axis %d because there are too many axes.",
+              getDeviceName().c_str(), mAxes.keyAt(i));
+        mAxes.removeItemsAt(i);
+    }
+}
+
+bool JoystickInputMapper::isCenteredAxis(int32_t axis) {
+    switch (axis) {
+        case AMOTION_EVENT_AXIS_X:
+        case AMOTION_EVENT_AXIS_Y:
+        case AMOTION_EVENT_AXIS_Z:
+        case AMOTION_EVENT_AXIS_RX:
+        case AMOTION_EVENT_AXIS_RY:
+        case AMOTION_EVENT_AXIS_RZ:
+        case AMOTION_EVENT_AXIS_HAT_X:
+        case AMOTION_EVENT_AXIS_HAT_Y:
+        case AMOTION_EVENT_AXIS_ORIENTATION:
+        case AMOTION_EVENT_AXIS_RUDDER:
+        case AMOTION_EVENT_AXIS_WHEEL:
+            return true;
+        default:
+            return false;
+    }
+}
+
+void JoystickInputMapper::reset(nsecs_t when) {
+    // Recenter all axes.
+    size_t numAxes = mAxes.size();
+    for (size_t i = 0; i < numAxes; i++) {
+        Axis& axis = mAxes.editValueAt(i);
+        axis.resetValue();
+    }
+
+    InputMapper::reset(when);
+}
+
+void JoystickInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+        case EV_ABS: {
+            ssize_t index = mAxes.indexOfKey(rawEvent->code);
+            if (index >= 0) {
+                Axis& axis = mAxes.editValueAt(index);
+                float newValue, highNewValue;
+                switch (axis.axisInfo.mode) {
+                    case AxisInfo::MODE_INVERT:
+                        newValue = (axis.rawAxisInfo.maxValue - rawEvent->value) * axis.scale +
+                                axis.offset;
+                        highNewValue = 0.0f;
+                        break;
+                    case AxisInfo::MODE_SPLIT:
+                        if (rawEvent->value < axis.axisInfo.splitValue) {
+                            newValue = (axis.axisInfo.splitValue - rawEvent->value) * axis.scale +
+                                    axis.offset;
+                            highNewValue = 0.0f;
+                        } else if (rawEvent->value > axis.axisInfo.splitValue) {
+                            newValue = 0.0f;
+                            highNewValue =
+                                    (rawEvent->value - axis.axisInfo.splitValue) * axis.highScale +
+                                    axis.highOffset;
+                        } else {
+                            newValue = 0.0f;
+                            highNewValue = 0.0f;
+                        }
+                        break;
+                    default:
+                        newValue = rawEvent->value * axis.scale + axis.offset;
+                        highNewValue = 0.0f;
+                        break;
+                }
+                axis.newValue = newValue;
+                axis.highNewValue = highNewValue;
+            }
+            break;
+        }
+
+        case EV_SYN:
+            switch (rawEvent->code) {
+                case SYN_REPORT:
+                    sync(rawEvent->when, false /*force*/);
+                    break;
+            }
+            break;
+    }
+}
+
+void JoystickInputMapper::sync(nsecs_t when, bool force) {
+    if (!filterAxes(force)) {
+        return;
+    }
+
+    int32_t metaState = mContext->getGlobalMetaState();
+    int32_t buttonState = 0;
+
+    PointerProperties pointerProperties;
+    pointerProperties.clear();
+    pointerProperties.id = 0;
+    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+    PointerCoords pointerCoords;
+    pointerCoords.clear();
+
+    size_t numAxes = mAxes.size();
+    for (size_t i = 0; i < numAxes; i++) {
+        const Axis& axis = mAxes.valueAt(i);
+        setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.axis, axis.currentValue);
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.highAxis,
+                                      axis.highCurrentValue);
+        }
+    }
+
+    // Moving a joystick axis should not wake the device because joysticks can
+    // be fairly noisy even when not in use.  On the other hand, pushing a gamepad
+    // button will likely wake the device.
+    // TODO: Use the input device configuration to control this behavior more finely.
+    uint32_t policyFlags = 0;
+
+    NotifyMotionArgs args(mContext->getNextSequenceNum(), when, getDeviceId(),
+                          AINPUT_SOURCE_JOYSTICK, ADISPLAY_ID_NONE, policyFlags,
+                          AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState,
+                          MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+                          &pointerProperties, &pointerCoords, 0, 0,
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
+    getListener()->notifyMotion(&args);
+}
+
+void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis,
+                                                    float value) {
+    pointerCoords->setAxisValue(axis, value);
+    /* In order to ease the transition for developers from using the old axes
+     * to the newer, more semantically correct axes, we'll continue to produce
+     * values for the old axes as mirrors of the value of their corresponding
+     * new axes. */
+    int32_t compatAxis = getCompatAxis(axis);
+    if (compatAxis >= 0) {
+        pointerCoords->setAxisValue(compatAxis, value);
+    }
+}
+
+bool JoystickInputMapper::filterAxes(bool force) {
+    bool atLeastOneSignificantChange = force;
+    size_t numAxes = mAxes.size();
+    for (size_t i = 0; i < numAxes; i++) {
+        Axis& axis = mAxes.editValueAt(i);
+        if (force ||
+            hasValueChangedSignificantly(axis.filter, axis.newValue, axis.currentValue, axis.min,
+                                         axis.max)) {
+            axis.currentValue = axis.newValue;
+            atLeastOneSignificantChange = true;
+        }
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            if (force ||
+                hasValueChangedSignificantly(axis.filter, axis.highNewValue, axis.highCurrentValue,
+                                             axis.min, axis.max)) {
+                axis.highCurrentValue = axis.highNewValue;
+                atLeastOneSignificantChange = true;
+            }
+        }
+    }
+    return atLeastOneSignificantChange;
+}
+
+bool JoystickInputMapper::hasValueChangedSignificantly(float filter, float newValue,
+                                                       float currentValue, float min, float max) {
+    if (newValue != currentValue) {
+        // Filter out small changes in value unless the value is converging on the axis
+        // bounds or center point.  This is intended to reduce the amount of information
+        // sent to applications by particularly noisy joysticks (such as PS3).
+        if (fabs(newValue - currentValue) > filter ||
+            hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, min) ||
+            hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, max) ||
+            hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, 0)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool JoystickInputMapper::hasMovedNearerToValueWithinFilteredRange(float filter, float newValue,
+                                                                   float currentValue,
+                                                                   float thresholdValue) {
+    float newDistance = fabs(newValue - thresholdValue);
+    if (newDistance < filter) {
+        float oldDistance = fabs(currentValue - thresholdValue);
+        if (newDistance < oldDistance) {
+            return true;
+        }
+    }
+    return false;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/InputReaderFactory.cpp b/services/inputflinger/reader/InputReaderFactory.cpp
new file mode 100644
index 0000000..7c23112
--- /dev/null
+++ b/services/inputflinger/reader/InputReaderFactory.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "InputReaderFactory.h"
+#include "InputReader.h"
+
+namespace android {
+
+sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
+                                           const sp<InputListenerInterface>& listener) {
+    return new InputReader(std::make_unique<EventHub>(), policy, listener);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/reader/TouchVideoDevice.cpp b/services/inputflinger/reader/TouchVideoDevice.cpp
new file mode 100644
index 0000000..c075078
--- /dev/null
+++ b/services/inputflinger/reader/TouchVideoDevice.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TouchVideoDevice.h"
+
+#define LOG_TAG "TouchVideoDevice"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <iostream>
+
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+using android::base::StringPrintf;
+using android::base::unique_fd;
+
+namespace android {
+
+TouchVideoDevice::TouchVideoDevice(int fd, std::string&& name, std::string&& devicePath,
+                                   uint32_t height, uint32_t width,
+                                   const std::array<const int16_t*, NUM_BUFFERS>& readLocations)
+      : mFd(fd),
+        mName(std::move(name)),
+        mPath(std::move(devicePath)),
+        mHeight(height),
+        mWidth(width),
+        mReadLocations(readLocations) {
+    mFrames.reserve(MAX_QUEUE_SIZE);
+};
+
+std::unique_ptr<TouchVideoDevice> TouchVideoDevice::create(std::string devicePath) {
+    unique_fd fd(open(devicePath.c_str(), O_RDWR | O_NONBLOCK));
+    if (fd.get() == INVALID_FD) {
+        ALOGE("Could not open video device %s: %s", devicePath.c_str(), strerror(errno));
+        return nullptr;
+    }
+
+    struct v4l2_capability cap;
+    int result = ioctl(fd.get(), VIDIOC_QUERYCAP, &cap);
+    if (result == -1) {
+        ALOGE("VIDIOC_QUERYCAP failed: %s", strerror(errno));
+        return nullptr;
+    }
+    if (!(cap.capabilities & V4L2_CAP_TOUCH)) {
+        ALOGE("Capability V4L2_CAP_TOUCH is not present, can't use device for heatmap data. "
+              "Make sure device specifies V4L2_CAP_TOUCH");
+        return nullptr;
+    }
+    ALOGI("Opening video device: driver = %s, card = %s, bus_info = %s, version = %i", cap.driver,
+          cap.card, cap.bus_info, cap.version);
+    std::string name = reinterpret_cast<const char*>(cap.card);
+
+    struct v4l2_input v4l2_input_struct;
+    v4l2_input_struct.index = 0;
+    result = ioctl(fd.get(), VIDIOC_ENUMINPUT, &v4l2_input_struct);
+    if (result == -1) {
+        ALOGE("VIDIOC_ENUMINPUT failed: %s", strerror(errno));
+        return nullptr;
+    }
+
+    if (v4l2_input_struct.type != V4L2_INPUT_TYPE_TOUCH) {
+        ALOGE("Video device does not provide touch data. "
+              "Make sure device specifies V4L2_INPUT_TYPE_TOUCH.");
+        return nullptr;
+    }
+
+    struct v4l2_format v4l2_fmt;
+    v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    result = ioctl(fd.get(), VIDIOC_G_FMT, &v4l2_fmt);
+    if (result == -1) {
+        ALOGE("VIDIOC_G_FMT failed: %s", strerror(errno));
+        return nullptr;
+    }
+    const uint32_t height = v4l2_fmt.fmt.pix.height;
+    const uint32_t width = v4l2_fmt.fmt.pix.width;
+    ALOGI("Frame dimensions: height = %" PRIu32 " width = %" PRIu32, height, width);
+
+    struct v4l2_requestbuffers req = {};
+    req.count = NUM_BUFFERS;
+    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    req.memory = V4L2_MEMORY_MMAP;
+    // req.reserved is zeroed during initialization, which is required per v4l docs
+    result = ioctl(fd.get(), VIDIOC_REQBUFS, &req);
+    if (result == -1) {
+        ALOGE("VIDIOC_REQBUFS failed: %s", strerror(errno));
+        return nullptr;
+    }
+    if (req.count != NUM_BUFFERS) {
+        ALOGE("Requested %zu buffers, but driver responded with count=%i", NUM_BUFFERS, req.count);
+        return nullptr;
+    }
+
+    struct v4l2_buffer buf = {};
+    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buf.memory = V4L2_MEMORY_MMAP;
+    // buf.reserved and buf.reserved2 are zeroed during initialization, required per v4l docs
+    std::array<const int16_t*, NUM_BUFFERS> readLocations;
+    for (size_t i = 0; i < NUM_BUFFERS; i++) {
+        buf.index = i;
+        result = ioctl(fd.get(), VIDIOC_QUERYBUF, &buf);
+        if (result == -1) {
+            ALOGE("VIDIOC_QUERYBUF failed: %s", strerror(errno));
+            return nullptr;
+        }
+        if (buf.length != height * width * sizeof(int16_t)) {
+            ALOGE("Unexpected value of buf.length = %i (offset = %" PRIu32 ")", buf.length,
+                  buf.m.offset);
+            return nullptr;
+        }
+
+        readLocations[i] = static_cast<const int16_t*>(
+                mmap(nullptr /* start anywhere */, buf.length, PROT_READ /* required */,
+                     MAP_SHARED /* recommended */, fd.get(), buf.m.offset));
+        if (readLocations[i] == MAP_FAILED) {
+            ALOGE("%s: map failed: %s", __func__, strerror(errno));
+            return nullptr;
+        }
+    }
+
+    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    result = ioctl(fd.get(), VIDIOC_STREAMON, &type);
+    if (result == -1) {
+        ALOGE("VIDIOC_STREAMON failed: %s", strerror(errno));
+        return nullptr;
+    }
+
+    for (size_t i = 0; i < NUM_BUFFERS; i++) {
+        buf.index = i;
+        result = ioctl(fd.get(), VIDIOC_QBUF, &buf);
+        if (result == -1) {
+            ALOGE("VIDIOC_QBUF failed for buffer %zu: %s", i, strerror(errno));
+            return nullptr;
+        }
+    }
+    // Using 'new' to access a non-public constructor.
+    return std::unique_ptr<TouchVideoDevice>(new TouchVideoDevice(fd.release(), std::move(name),
+                                                                  std::move(devicePath), height,
+                                                                  width, readLocations));
+}
+
+size_t TouchVideoDevice::readAndQueueFrames() {
+    std::vector<TouchVideoFrame> frames = readFrames();
+    const size_t numFrames = frames.size();
+    if (numFrames == 0) {
+        // Likely an error occurred
+        return 0;
+    }
+    // Concatenate the vectors, then clip up to maximum size allowed
+    mFrames.insert(mFrames.end(), std::make_move_iterator(frames.begin()),
+                   std::make_move_iterator(frames.end()));
+    if (mFrames.size() > MAX_QUEUE_SIZE) {
+        ALOGE("More than %zu frames have been accumulated. Dropping %zu frames", MAX_QUEUE_SIZE,
+              mFrames.size() - MAX_QUEUE_SIZE);
+        mFrames.erase(mFrames.begin(), mFrames.end() - MAX_QUEUE_SIZE);
+    }
+    return numFrames;
+}
+
+std::vector<TouchVideoFrame> TouchVideoDevice::consumeFrames() {
+    std::vector<TouchVideoFrame> frames = std::move(mFrames);
+    mFrames = {};
+    return frames;
+}
+
+std::optional<TouchVideoFrame> TouchVideoDevice::readFrame() {
+    struct v4l2_buffer buf = {};
+    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buf.memory = V4L2_MEMORY_MMAP;
+    int result = ioctl(mFd.get(), VIDIOC_DQBUF, &buf);
+    if (result == -1) {
+        // EAGAIN means we've reached the end of the read buffer, so it's expected.
+        if (errno != EAGAIN) {
+            ALOGE("VIDIOC_DQBUF failed: %s", strerror(errno));
+        }
+        return std::nullopt;
+    }
+    if ((buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC) {
+        // We use CLOCK_MONOTONIC for input events, so if the clocks don't match,
+        // we can't compare timestamps. Just log a warning, since this is a driver issue
+        ALOGW("The timestamp %ld.%ld was not acquired using CLOCK_MONOTONIC", buf.timestamp.tv_sec,
+              buf.timestamp.tv_usec);
+    }
+    std::vector<int16_t> data(mHeight * mWidth);
+    const int16_t* readFrom = mReadLocations[buf.index];
+    std::copy(readFrom, readFrom + mHeight * mWidth, data.begin());
+    TouchVideoFrame frame(mHeight, mWidth, std::move(data), buf.timestamp);
+
+    result = ioctl(mFd.get(), VIDIOC_QBUF, &buf);
+    if (result == -1) {
+        ALOGE("VIDIOC_QBUF failed: %s", strerror(errno));
+    }
+    return std::make_optional(std::move(frame));
+}
+
+/*
+ * This function should not be called unless buffer is ready! This must be checked with
+ * select, poll, epoll, or some other similar api first.
+ * The oldest frame will be at the beginning of the array.
+ */
+std::vector<TouchVideoFrame> TouchVideoDevice::readFrames() {
+    std::vector<TouchVideoFrame> frames;
+    while (true) {
+        std::optional<TouchVideoFrame> frame = readFrame();
+        if (!frame) {
+            break;
+        }
+        frames.push_back(std::move(*frame));
+    }
+    return frames;
+}
+
+TouchVideoDevice::~TouchVideoDevice() {
+    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    int result = ioctl(mFd.get(), VIDIOC_STREAMOFF, &type);
+    if (result == -1) {
+        ALOGE("VIDIOC_STREAMOFF failed: %s", strerror(errno));
+    }
+    for (const int16_t* buffer : mReadLocations) {
+        void* bufferAddress = static_cast<void*>(const_cast<int16_t*>(buffer));
+        result = munmap(bufferAddress, mHeight * mWidth * sizeof(int16_t));
+        if (result == -1) {
+            ALOGE("%s: Couldn't unmap: [%s]", __func__, strerror(errno));
+        }
+    }
+}
+
+std::string TouchVideoDevice::dump() const {
+    return StringPrintf("Video device %s (%s) : height=%" PRIu32 ", width=%" PRIu32
+                        ", fd=%i, hasValidFd=%s",
+                        mName.c_str(), mPath.c_str(), mHeight, mWidth, mFd.get(),
+                        hasValidFd() ? "true" : "false");
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
new file mode 100644
index 0000000..da0f497
--- /dev/null
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+#ifndef _RUNTIME_EVENT_HUB_H
+#define _RUNTIME_EVENT_HUB_H
+
+#include <vector>
+
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/KeyCharacterMap.h>
+#include <input/KeyLayoutMap.h>
+#include <input/Keyboard.h>
+#include <input/VirtualKeyMap.h>
+#include <utils/BitSet.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/List.h>
+#include <utils/Log.h>
+#include <utils/Mutex.h>
+#include <utils/PropertyMap.h>
+
+#include <linux/input.h>
+#include <sys/epoll.h>
+
+#include "TouchVideoDevice.h"
+
+/* Convenience constants. */
+
+#define BTN_FIRST 0x100 // first button code
+#define BTN_LAST 0x15f  // last button code
+
+namespace android {
+
+/*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+    nsecs_t when;
+    int32_t deviceId;
+    int32_t type;
+    int32_t code;
+    int32_t value;
+};
+
+/* Describes an absolute axis. */
+struct RawAbsoluteAxisInfo {
+    bool valid; // true if the information is valid, false otherwise
+
+    int32_t minValue;   // minimum value
+    int32_t maxValue;   // maximum value
+    int32_t flat;       // center flat position, eg. flat == 8 means center is between -8 and 8
+    int32_t fuzz;       // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+    int32_t resolution; // resolution in units per mm or radians per mm
+
+    inline void clear() {
+        valid = false;
+        minValue = 0;
+        maxValue = 0;
+        flat = 0;
+        fuzz = 0;
+        resolution = 0;
+    }
+};
+
+/*
+ * Input device classes.
+ */
+enum {
+    /* The input device is a keyboard or has buttons. */
+    INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001,
+
+    /* The input device is an alpha-numeric keyboard (not just a dial pad). */
+    INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002,
+
+    /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
+    INPUT_DEVICE_CLASS_TOUCH = 0x00000004,
+
+    /* The input device is a cursor device such as a trackball or mouse. */
+    INPUT_DEVICE_CLASS_CURSOR = 0x00000008,
+
+    /* The input device is a multi-touch touchscreen. */
+    INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010,
+
+    /* The input device is a directional pad (implies keyboard, has DPAD keys). */
+    INPUT_DEVICE_CLASS_DPAD = 0x00000020,
+
+    /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
+    INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040,
+
+    /* The input device has switches. */
+    INPUT_DEVICE_CLASS_SWITCH = 0x00000080,
+
+    /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
+    INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,
+
+    /* The input device has a vibrator (supports FF_RUMBLE). */
+    INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,
+
+    /* The input device has a microphone. */
+    INPUT_DEVICE_CLASS_MIC = 0x00000400,
+
+    /* The input device is an external stylus (has data we want to fuse with touch data). */
+    INPUT_DEVICE_CLASS_EXTERNAL_STYLUS = 0x00000800,
+
+    /* The input device has a rotary encoder */
+    INPUT_DEVICE_CLASS_ROTARY_ENCODER = 0x00001000,
+
+    /* The input device is virtual (not a real device, not part of UI configuration). */
+    INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,
+
+    /* The input device is external (not built-in). */
+    INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000,
+};
+
+/*
+ * Gets the class that owns an axis, in cases where multiple classes might claim
+ * the same axis for different purposes.
+ */
+extern uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses);
+
+/*
+ * Grand Central Station for events.
+ *
+ * The event hub aggregates input events received across all known input
+ * devices on the system, including devices that may be emulated by the simulator
+ * environment.  In addition, the event hub generates fake input events to indicate
+ * when devices are added or removed.
+ *
+ * The event hub provides a stream of input events (via the getEvent function).
+ * It also supports querying the current actual state of input devices such as identifying
+ * which keys are currently down.  Finally, the event hub keeps track of the capabilities of
+ * individual input devices, such as their class and the set of key codes that they support.
+ */
+class EventHubInterface {
+public:
+    EventHubInterface() {}
+    virtual ~EventHubInterface() {}
+
+    // Synthetic raw event type codes produced when devices are added or removed.
+    enum {
+        // Sent when a device is added.
+        DEVICE_ADDED = 0x10000000,
+        // Sent when a device is removed.
+        DEVICE_REMOVED = 0x20000000,
+        // Sent when all added/removed devices from the most recent scan have been reported.
+        // This event is always sent at least once.
+        FINISHED_DEVICE_SCAN = 0x30000000,
+
+        FIRST_SYNTHETIC_EVENT = DEVICE_ADDED,
+    };
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
+
+    virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const = 0;
+
+    virtual int32_t getDeviceControllerNumber(int32_t deviceId) const = 0;
+
+    virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0;
+
+    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+                                         RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+
+    virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
+
+    virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
+
+    virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+                            int32_t metaState, int32_t* outKeycode, int32_t* outMetaState,
+                            uint32_t* outFlags) const = 0;
+
+    virtual status_t mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const = 0;
+
+    // Sets devices that are excluded from opening.
+    // This can be used to ignore input devices for sensors.
+    virtual void setExcludedDevices(const std::vector<std::string>& devices) = 0;
+
+    /*
+     * Wait for events to become available and returns them.
+     * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
+     * This ensures that the device will not go to sleep while the event is being processed.
+     * If the device needs to remain awake longer than that, then the caller is responsible
+     * for taking care of it (say, by poking the power manager user activity timer).
+     *
+     * The timeout is advisory only.  If the device is asleep, it will not wake just to
+     * service the timeout.
+     *
+     * Returns the number of events obtained, or 0 if the timeout expired.
+     */
+    virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0;
+    virtual std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) = 0;
+
+    /*
+     * Query current input state.
+     */
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
+    virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
+                                          int32_t* outValue) const = 0;
+
+    /*
+     * Examine key input devices for specific framework keycode support
+     */
+    virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+                                       uint8_t* outFlags) const = 0;
+
+    virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0;
+
+    /* LED related functions expect Android LED constants, not scan codes or HID usages */
+    virtual bool hasLed(int32_t deviceId, int32_t led) const = 0;
+    virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0;
+
+    virtual void getVirtualKeyDefinitions(
+            int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys) const = 0;
+
+    virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const = 0;
+    virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map) = 0;
+
+    /* Control the vibrator. */
+    virtual void vibrate(int32_t deviceId, nsecs_t duration) = 0;
+    virtual void cancelVibrate(int32_t deviceId) = 0;
+
+    /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
+    virtual void requestReopenDevices() = 0;
+
+    /* Wakes up getEvents() if it is blocked on a read. */
+    virtual void wake() = 0;
+
+    /* Dump EventHub state to a string. */
+    virtual void dump(std::string& dump) = 0;
+
+    /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+    virtual void monitor() = 0;
+
+    /* Return true if the device is enabled. */
+    virtual bool isDeviceEnabled(int32_t deviceId) = 0;
+
+    /* Enable an input device */
+    virtual status_t enableDevice(int32_t deviceId) = 0;
+
+    /* Disable an input device. Closes file descriptor to that device. */
+    virtual status_t disableDevice(int32_t deviceId) = 0;
+};
+
+class EventHub : public EventHubInterface {
+public:
+    EventHub();
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const;
+
+    virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const;
+
+    virtual int32_t getDeviceControllerNumber(int32_t deviceId) const;
+
+    virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const;
+
+    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+                                         RawAbsoluteAxisInfo* outAxisInfo) const;
+
+    virtual bool hasRelativeAxis(int32_t deviceId, int axis) const;
+
+    virtual bool hasInputProperty(int32_t deviceId, int property) const;
+
+    virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+                            int32_t metaState, int32_t* outKeycode, int32_t* outMetaState,
+                            uint32_t* outFlags) const;
+
+    virtual status_t mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const;
+
+    virtual void setExcludedDevices(const std::vector<std::string>& devices);
+
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const;
+    virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const;
+
+    virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+                                       uint8_t* outFlags) const;
+
+    virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize);
+    virtual std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId);
+
+    virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const;
+    virtual bool hasLed(int32_t deviceId, int32_t led) const;
+    virtual void setLedState(int32_t deviceId, int32_t led, bool on);
+
+    virtual void getVirtualKeyDefinitions(int32_t deviceId,
+                                          std::vector<VirtualKeyDefinition>& outVirtualKeys) const;
+
+    virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const;
+    virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map);
+
+    virtual void vibrate(int32_t deviceId, nsecs_t duration);
+    virtual void cancelVibrate(int32_t deviceId);
+
+    virtual void requestReopenDevices();
+
+    virtual void wake();
+
+    virtual void dump(std::string& dump);
+    virtual void monitor();
+
+    virtual ~EventHub();
+
+private:
+    struct Device {
+        Device* next;
+
+        int fd; // may be -1 if device is closed
+        const int32_t id;
+        const std::string path;
+        const InputDeviceIdentifier identifier;
+
+        std::unique_ptr<TouchVideoDevice> videoDevice;
+
+        uint32_t classes;
+
+        uint8_t keyBitmask[(KEY_MAX + 1) / 8];
+        uint8_t absBitmask[(ABS_MAX + 1) / 8];
+        uint8_t relBitmask[(REL_MAX + 1) / 8];
+        uint8_t swBitmask[(SW_MAX + 1) / 8];
+        uint8_t ledBitmask[(LED_MAX + 1) / 8];
+        uint8_t ffBitmask[(FF_MAX + 1) / 8];
+        uint8_t propBitmask[(INPUT_PROP_MAX + 1) / 8];
+
+        std::string configurationFile;
+        PropertyMap* configuration;
+        std::unique_ptr<VirtualKeyMap> virtualKeyMap;
+        KeyMap keyMap;
+
+        sp<KeyCharacterMap> overlayKeyMap;
+        sp<KeyCharacterMap> combinedKeyMap;
+
+        bool ffEffectPlaying;
+        int16_t ffEffectId; // initially -1
+
+        int32_t controllerNumber;
+
+        Device(int fd, int32_t id, const std::string& path,
+               const InputDeviceIdentifier& identifier);
+        ~Device();
+
+        void close();
+
+        bool enabled; // initially true
+        status_t enable();
+        status_t disable();
+        bool hasValidFd();
+        const bool isVirtual; // set if fd < 0 is passed to constructor
+
+        const sp<KeyCharacterMap>& getKeyCharacterMap() const {
+            if (combinedKeyMap != nullptr) {
+                return combinedKeyMap;
+            }
+            return keyMap.keyCharacterMap;
+        }
+    };
+
+    status_t openDeviceLocked(const char* devicePath);
+    void openVideoDeviceLocked(const std::string& devicePath);
+    void createVirtualKeyboardLocked();
+    void addDeviceLocked(Device* device);
+    void assignDescriptorLocked(InputDeviceIdentifier& identifier);
+
+    void closeDeviceByPathLocked(const char* devicePath);
+    void closeVideoDeviceByPathLocked(const std::string& devicePath);
+    void closeDeviceLocked(Device* device);
+    void closeAllDevicesLocked();
+
+    void configureFd(Device* device);
+
+    bool isDeviceEnabled(int32_t deviceId);
+    status_t enableDevice(int32_t deviceId);
+    status_t disableDevice(int32_t deviceId);
+    status_t registerFdForEpoll(int fd);
+    status_t unregisterFdFromEpoll(int fd);
+    status_t registerDeviceForEpollLocked(Device* device);
+    void registerVideoDeviceForEpollLocked(const TouchVideoDevice& videoDevice);
+    status_t unregisterDeviceFromEpollLocked(Device* device);
+    void unregisterVideoDeviceFromEpollLocked(const TouchVideoDevice& videoDevice);
+
+    status_t scanDirLocked(const char* dirname);
+    status_t scanVideoDirLocked(const std::string& dirname);
+    void scanDevicesLocked();
+    status_t readNotifyLocked();
+
+    Device* getDeviceByDescriptorLocked(const std::string& descriptor) const;
+    Device* getDeviceLocked(int32_t deviceId) const;
+    Device* getDeviceByPathLocked(const char* devicePath) const;
+    /**
+     * Look through all available fd's (both for input devices and for video devices),
+     * and return the device pointer.
+     */
+    Device* getDeviceByFdLocked(int fd) const;
+
+    bool hasKeycodeLocked(Device* device, int keycode) const;
+
+    void loadConfigurationLocked(Device* device);
+    bool loadVirtualKeyMapLocked(Device* device);
+    status_t loadKeyMapLocked(Device* device);
+
+    bool isExternalDeviceLocked(Device* device);
+    bool deviceHasMicLocked(Device* device);
+
+    int32_t getNextControllerNumberLocked(Device* device);
+    void releaseControllerNumberLocked(Device* device);
+    void setLedForControllerLocked(Device* device);
+
+    status_t mapLed(Device* device, int32_t led, int32_t* outScanCode) const;
+    void setLedStateLocked(Device* device, int32_t led, bool on);
+
+    // Protect all internal state.
+    mutable Mutex mLock;
+
+    // The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
+    // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
+    enum {
+        // Must not conflict with any other assigned device ids, including
+        // the virtual keyboard id (-1).
+        NO_BUILT_IN_KEYBOARD = -2,
+    };
+    int32_t mBuiltInKeyboardId;
+
+    int32_t mNextDeviceId;
+
+    BitSet32 mControllerNumbers;
+
+    KeyedVector<int32_t, Device*> mDevices;
+    /**
+     * Video devices that report touchscreen heatmap, but have not (yet) been paired
+     * with a specific input device. Video device discovery is independent from input device
+     * discovery, so the two types of devices could be found in any order.
+     * Ideally, video devices in this queue do not have an open fd, or at least aren't
+     * actively streaming.
+     */
+    std::vector<std::unique_ptr<TouchVideoDevice>> mUnattachedVideoDevices;
+
+    Device* mOpeningDevices;
+    Device* mClosingDevices;
+
+    bool mNeedToSendFinishedDeviceScan;
+    bool mNeedToReopenDevices;
+    bool mNeedToScanDevices;
+    std::vector<std::string> mExcludedDevices;
+
+    int mEpollFd;
+    int mINotifyFd;
+    int mWakeReadPipeFd;
+    int mWakeWritePipeFd;
+
+    int mInputWd;
+    int mVideoWd;
+
+    // Maximum number of signalled FDs to handle at a time.
+    static const int EPOLL_MAX_EVENTS = 16;
+
+    // The array of pending epoll events and the index of the next event to be handled.
+    struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
+    size_t mPendingEventCount;
+    size_t mPendingEventIndex;
+    bool mPendingINotify;
+};
+
+}; // namespace android
+
+#endif // _RUNTIME_EVENT_HUB_H
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
new file mode 100644
index 0000000..1b8177c
--- /dev/null
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -0,0 +1,1706 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_READER_H
+#define _UI_INPUT_READER_H
+
+#include "EventHub.h"
+#include "InputListener.h"
+#include "InputReaderBase.h"
+#include "PointerControllerInterface.h"
+
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <input/VelocityControl.h>
+#include <input/VelocityTracker.h>
+#include <ui/DisplayInfo.h>
+#include <utils/BitSet.h>
+#include <utils/Condition.h>
+#include <utils/KeyedVector.h>
+#include <utils/Mutex.h>
+#include <utils/Timers.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <optional>
+#include <vector>
+
+namespace android {
+
+class InputDevice;
+class InputMapper;
+
+struct StylusState {
+    /* Time the stylus event was received. */
+    nsecs_t when;
+    /* Pressure as reported by the stylus, normalized to the range [0, 1.0]. */
+    float pressure;
+    /* The state of the stylus buttons as a bitfield (e.g. AMOTION_EVENT_BUTTON_SECONDARY). */
+    uint32_t buttons;
+    /* Which tool type the stylus is currently using (e.g. AMOTION_EVENT_TOOL_TYPE_ERASER). */
+    int32_t toolType;
+
+    void copyFrom(const StylusState& other) {
+        when = other.when;
+        pressure = other.pressure;
+        buttons = other.buttons;
+        toolType = other.toolType;
+    }
+
+    void clear() {
+        when = LLONG_MAX;
+        pressure = 0.f;
+        buttons = 0;
+        toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+    }
+};
+
+/* Internal interface used by individual input devices to access global input device state
+ * and parameters maintained by the input reader.
+ */
+class InputReaderContext {
+public:
+    InputReaderContext() {}
+    virtual ~InputReaderContext() {}
+
+    virtual void updateGlobalMetaState() = 0;
+    virtual int32_t getGlobalMetaState() = 0;
+
+    virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
+    virtual bool shouldDropVirtualKey(nsecs_t now, InputDevice* device, int32_t keyCode,
+                                      int32_t scanCode) = 0;
+
+    virtual void fadePointer() = 0;
+
+    virtual void requestTimeoutAtTime(nsecs_t when) = 0;
+    virtual int32_t bumpGeneration() = 0;
+
+    virtual void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) = 0;
+    virtual void dispatchExternalStylusState(const StylusState& outState) = 0;
+
+    virtual InputReaderPolicyInterface* getPolicy() = 0;
+    virtual InputListenerInterface* getListener() = 0;
+    virtual EventHubInterface* getEventHub() = 0;
+
+    virtual uint32_t getNextSequenceNum() = 0;
+};
+
+/* The input reader reads raw event data from the event hub and processes it into input events
+ * that it sends to the input listener.  Some functions of the input reader, such as early
+ * event filtering in low power states, are controlled by a separate policy object.
+ *
+ * The InputReader owns a collection of InputMappers.  Most of the work it does happens
+ * on the input reader thread but the InputReader can receive queries from other system
+ * components running on arbitrary threads.  To keep things manageable, the InputReader
+ * uses a single Mutex to guard its state.  The Mutex may be held while calling into the
+ * EventHub or the InputReaderPolicy but it is never held while calling into the
+ * InputListener.
+ */
+class InputReader : public InputReaderInterface {
+public:
+    InputReader(std::shared_ptr<EventHubInterface> eventHub,
+                const sp<InputReaderPolicyInterface>& policy,
+                const sp<InputListenerInterface>& listener);
+    virtual ~InputReader();
+
+    virtual void dump(std::string& dump);
+    virtual void monitor();
+
+    virtual void loopOnce();
+
+    virtual void getInputDevices(std::vector<InputDeviceInfo>& outInputDevices);
+
+    virtual bool isInputDeviceEnabled(int32_t deviceId);
+
+    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode);
+    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode);
+    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw);
+
+    virtual void toggleCapsLockState(int32_t deviceId);
+
+    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+                         const int32_t* keyCodes, uint8_t* outFlags);
+
+    virtual void requestRefreshConfiguration(uint32_t changes);
+
+    virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+                         ssize_t repeat, int32_t token);
+    virtual void cancelVibrate(int32_t deviceId, int32_t token);
+
+    virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId);
+
+protected:
+    // These members are protected so they can be instrumented by test cases.
+    virtual InputDevice* createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
+                                            const InputDeviceIdentifier& identifier,
+                                            uint32_t classes);
+
+    class ContextImpl : public InputReaderContext {
+        InputReader* mReader;
+
+    public:
+        explicit ContextImpl(InputReader* reader);
+
+        virtual void updateGlobalMetaState();
+        virtual int32_t getGlobalMetaState();
+        virtual void disableVirtualKeysUntil(nsecs_t time);
+        virtual bool shouldDropVirtualKey(nsecs_t now, InputDevice* device, int32_t keyCode,
+                                          int32_t scanCode);
+        virtual void fadePointer();
+        virtual void requestTimeoutAtTime(nsecs_t when);
+        virtual int32_t bumpGeneration();
+        virtual void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices);
+        virtual void dispatchExternalStylusState(const StylusState& outState);
+        virtual InputReaderPolicyInterface* getPolicy();
+        virtual InputListenerInterface* getListener();
+        virtual EventHubInterface* getEventHub();
+        virtual uint32_t getNextSequenceNum();
+    } mContext;
+
+    friend class ContextImpl;
+
+private:
+    Mutex mLock;
+
+    Condition mReaderIsAliveCondition;
+
+    // This could be unique_ptr, but due to the way InputReader tests are written,
+    // it is made shared_ptr here. In the tests, an EventHub reference is retained by the test
+    // in parallel to passing it to the InputReader.
+    std::shared_ptr<EventHubInterface> mEventHub;
+    sp<InputReaderPolicyInterface> mPolicy;
+    sp<QueuedInputListener> mQueuedListener;
+
+    InputReaderConfiguration mConfig;
+
+    // used by InputReaderContext::getNextSequenceNum() as a counter for event sequence numbers
+    uint32_t mNextSequenceNum;
+
+    // The event queue.
+    static const int EVENT_BUFFER_SIZE = 256;
+    RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
+
+    KeyedVector<int32_t, InputDevice*> mDevices;
+
+    // low-level input event decoding and device management
+    void processEventsLocked(const RawEvent* rawEvents, size_t count);
+
+    void addDeviceLocked(nsecs_t when, int32_t deviceId);
+    void removeDeviceLocked(nsecs_t when, int32_t deviceId);
+    void processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count);
+    void timeoutExpiredLocked(nsecs_t when);
+
+    void handleConfigurationChangedLocked(nsecs_t when);
+
+    int32_t mGlobalMetaState;
+    void updateGlobalMetaStateLocked();
+    int32_t getGlobalMetaStateLocked();
+
+    void notifyExternalStylusPresenceChanged();
+    void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices);
+    void dispatchExternalStylusState(const StylusState& state);
+
+    void fadePointerLocked();
+
+    int32_t mGeneration;
+    int32_t bumpGenerationLocked();
+
+    void getInputDevicesLocked(std::vector<InputDeviceInfo>& outInputDevices);
+
+    nsecs_t mDisableVirtualKeysTimeout;
+    void disableVirtualKeysUntilLocked(nsecs_t time);
+    bool shouldDropVirtualKeyLocked(nsecs_t now, InputDevice* device, int32_t keyCode,
+                                    int32_t scanCode);
+
+    nsecs_t mNextTimeout;
+    void requestTimeoutAtTimeLocked(nsecs_t when);
+
+    uint32_t mConfigurationChangesToRefresh;
+    void refreshConfigurationLocked(uint32_t changes);
+
+    // state queries
+    typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+    int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+                           GetStateFunc getStateFunc);
+    bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+                                     const int32_t* keyCodes, uint8_t* outFlags);
+};
+
+/* Represents the state of a single input device. */
+class InputDevice {
+public:
+    InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+                int32_t controllerNumber, const InputDeviceIdentifier& identifier,
+                uint32_t classes);
+    ~InputDevice();
+
+    inline InputReaderContext* getContext() { return mContext; }
+    inline int32_t getId() const { return mId; }
+    inline int32_t getControllerNumber() const { return mControllerNumber; }
+    inline int32_t getGeneration() const { return mGeneration; }
+    inline const std::string getName() const { return mIdentifier.name; }
+    inline const std::string getDescriptor() { return mIdentifier.descriptor; }
+    inline uint32_t getClasses() const { return mClasses; }
+    inline uint32_t getSources() const { return mSources; }
+
+    inline bool isExternal() { return mIsExternal; }
+    inline void setExternal(bool external) { mIsExternal = external; }
+    inline std::optional<uint8_t> getAssociatedDisplayPort() const {
+        return mAssociatedDisplayPort;
+    }
+    inline std::optional<DisplayViewport> getAssociatedViewport() const {
+        return mAssociatedViewport;
+    }
+    inline void setMic(bool hasMic) { mHasMic = hasMic; }
+    inline bool hasMic() const { return mHasMic; }
+
+    inline bool isIgnored() { return mMappers.empty(); }
+
+    bool isEnabled();
+    void setEnabled(bool enabled, nsecs_t when);
+
+    void dump(std::string& dump);
+    void addMapper(InputMapper* mapper);
+    void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    void reset(nsecs_t when);
+    void process(const RawEvent* rawEvents, size_t count);
+    void timeoutExpired(nsecs_t when);
+    void updateExternalStylusState(const StylusState& state);
+
+    void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
+    int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
+                               uint8_t* outFlags);
+    void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+    void cancelVibrate(int32_t token);
+    void cancelTouch(nsecs_t when);
+
+    int32_t getMetaState();
+    void updateMetaState(int32_t keyCode);
+
+    void fadePointer();
+
+    void bumpGeneration();
+
+    void notifyReset(nsecs_t when);
+
+    inline const PropertyMap& getConfiguration() { return mConfiguration; }
+    inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+    bool hasKey(int32_t code) { return getEventHub()->hasScanCode(mId, code); }
+
+    bool hasAbsoluteAxis(int32_t code) {
+        RawAbsoluteAxisInfo info;
+        getEventHub()->getAbsoluteAxisInfo(mId, code, &info);
+        return info.valid;
+    }
+
+    bool isKeyPressed(int32_t code) {
+        return getEventHub()->getScanCodeState(mId, code) == AKEY_STATE_DOWN;
+    }
+
+    int32_t getAbsoluteAxisValue(int32_t code) {
+        int32_t value;
+        getEventHub()->getAbsoluteAxisValue(mId, code, &value);
+        return value;
+    }
+
+    std::optional<int32_t> getAssociatedDisplayId();
+
+private:
+    InputReaderContext* mContext;
+    int32_t mId;
+    int32_t mGeneration;
+    int32_t mControllerNumber;
+    InputDeviceIdentifier mIdentifier;
+    std::string mAlias;
+    uint32_t mClasses;
+
+    std::vector<InputMapper*> mMappers;
+
+    uint32_t mSources;
+    bool mIsExternal;
+    std::optional<uint8_t> mAssociatedDisplayPort;
+    std::optional<DisplayViewport> mAssociatedViewport;
+    bool mHasMic;
+    bool mDropUntilNextSync;
+
+    typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+    int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+
+    PropertyMap mConfiguration;
+};
+
+/* Keeps track of the state of mouse or touch pad buttons. */
+class CursorButtonAccumulator {
+public:
+    CursorButtonAccumulator();
+    void reset(InputDevice* device);
+
+    void process(const RawEvent* rawEvent);
+
+    uint32_t getButtonState() const;
+
+private:
+    bool mBtnLeft;
+    bool mBtnRight;
+    bool mBtnMiddle;
+    bool mBtnBack;
+    bool mBtnSide;
+    bool mBtnForward;
+    bool mBtnExtra;
+    bool mBtnTask;
+
+    void clearButtons();
+};
+
+/* Keeps track of cursor movements. */
+
+class CursorMotionAccumulator {
+public:
+    CursorMotionAccumulator();
+    void reset(InputDevice* device);
+
+    void process(const RawEvent* rawEvent);
+    void finishSync();
+
+    inline int32_t getRelativeX() const { return mRelX; }
+    inline int32_t getRelativeY() const { return mRelY; }
+
+private:
+    int32_t mRelX;
+    int32_t mRelY;
+
+    void clearRelativeAxes();
+};
+
+/* Keeps track of cursor scrolling motions. */
+
+class CursorScrollAccumulator {
+public:
+    CursorScrollAccumulator();
+    void configure(InputDevice* device);
+    void reset(InputDevice* device);
+
+    void process(const RawEvent* rawEvent);
+    void finishSync();
+
+    inline bool haveRelativeVWheel() const { return mHaveRelWheel; }
+    inline bool haveRelativeHWheel() const { return mHaveRelHWheel; }
+
+    inline int32_t getRelativeX() const { return mRelX; }
+    inline int32_t getRelativeY() const { return mRelY; }
+    inline int32_t getRelativeVWheel() const { return mRelWheel; }
+    inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+
+private:
+    bool mHaveRelWheel;
+    bool mHaveRelHWheel;
+
+    int32_t mRelX;
+    int32_t mRelY;
+    int32_t mRelWheel;
+    int32_t mRelHWheel;
+
+    void clearRelativeAxes();
+};
+
+/* Keeps track of the state of touch, stylus and tool buttons. */
+class TouchButtonAccumulator {
+public:
+    TouchButtonAccumulator();
+    void configure(InputDevice* device);
+    void reset(InputDevice* device);
+
+    void process(const RawEvent* rawEvent);
+
+    uint32_t getButtonState() const;
+    int32_t getToolType() const;
+    bool isToolActive() const;
+    bool isHovering() const;
+    bool hasStylus() const;
+
+private:
+    bool mHaveBtnTouch;
+    bool mHaveStylus;
+
+    bool mBtnTouch;
+    bool mBtnStylus;
+    bool mBtnStylus2;
+    bool mBtnToolFinger;
+    bool mBtnToolPen;
+    bool mBtnToolRubber;
+    bool mBtnToolBrush;
+    bool mBtnToolPencil;
+    bool mBtnToolAirbrush;
+    bool mBtnToolMouse;
+    bool mBtnToolLens;
+    bool mBtnToolDoubleTap;
+    bool mBtnToolTripleTap;
+    bool mBtnToolQuadTap;
+
+    void clearButtons();
+};
+
+/* Raw axis information from the driver. */
+struct RawPointerAxes {
+    RawAbsoluteAxisInfo x;
+    RawAbsoluteAxisInfo y;
+    RawAbsoluteAxisInfo pressure;
+    RawAbsoluteAxisInfo touchMajor;
+    RawAbsoluteAxisInfo touchMinor;
+    RawAbsoluteAxisInfo toolMajor;
+    RawAbsoluteAxisInfo toolMinor;
+    RawAbsoluteAxisInfo orientation;
+    RawAbsoluteAxisInfo distance;
+    RawAbsoluteAxisInfo tiltX;
+    RawAbsoluteAxisInfo tiltY;
+    RawAbsoluteAxisInfo trackingId;
+    RawAbsoluteAxisInfo slot;
+
+    RawPointerAxes();
+    inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; }
+    inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; }
+    void clear();
+};
+
+/* Raw data for a collection of pointers including a pointer id mapping table. */
+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;
+    };
+
+    uint32_t pointerCount;
+    Pointer pointers[MAX_POINTERS];
+    BitSet32 hoveringIdBits, touchingIdBits;
+    uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+    RawPointerData();
+    void clear();
+    void copyFrom(const RawPointerData& other);
+    void getCentroidOfTouchingPointers(float* outX, float* outY) const;
+
+    inline void markIdBit(uint32_t id, bool isHovering) {
+        if (isHovering) {
+            hoveringIdBits.markBit(id);
+        } else {
+            touchingIdBits.markBit(id);
+        }
+    }
+
+    inline void clearIdBits() {
+        hoveringIdBits.clear();
+        touchingIdBits.clear();
+    }
+
+    inline const Pointer& pointerForId(uint32_t id) const { return pointers[idToIndex[id]]; }
+
+    inline bool isHovering(uint32_t pointerIndex) { return pointers[pointerIndex].isHovering; }
+};
+
+/* Cooked data for a collection of pointers including a pointer id mapping table. */
+struct CookedPointerData {
+    uint32_t pointerCount;
+    PointerProperties pointerProperties[MAX_POINTERS];
+    PointerCoords pointerCoords[MAX_POINTERS];
+    BitSet32 hoveringIdBits, touchingIdBits;
+    uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+    CookedPointerData();
+    void clear();
+    void copyFrom(const CookedPointerData& other);
+
+    inline const PointerCoords& pointerCoordsForId(uint32_t id) const {
+        return pointerCoords[idToIndex[id]];
+    }
+
+    inline PointerCoords& editPointerCoordsWithId(uint32_t id) {
+        return pointerCoords[idToIndex[id]];
+    }
+
+    inline PointerProperties& editPointerPropertiesWithId(uint32_t id) {
+        return pointerProperties[idToIndex[id]];
+    }
+
+    inline bool isHovering(uint32_t pointerIndex) const {
+        return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id);
+    }
+
+    inline bool isTouching(uint32_t pointerIndex) const {
+        return touchingIdBits.hasBit(pointerProperties[pointerIndex].id);
+    }
+};
+
+/* Keeps track of the state of single-touch protocol. */
+class SingleTouchMotionAccumulator {
+public:
+    SingleTouchMotionAccumulator();
+
+    void process(const RawEvent* rawEvent);
+    void reset(InputDevice* device);
+
+    inline int32_t getAbsoluteX() const { return mAbsX; }
+    inline int32_t getAbsoluteY() const { return mAbsY; }
+    inline int32_t getAbsolutePressure() const { return mAbsPressure; }
+    inline int32_t getAbsoluteToolWidth() const { return mAbsToolWidth; }
+    inline int32_t getAbsoluteDistance() const { return mAbsDistance; }
+    inline int32_t getAbsoluteTiltX() const { return mAbsTiltX; }
+    inline int32_t getAbsoluteTiltY() const { return mAbsTiltY; }
+
+private:
+    int32_t mAbsX;
+    int32_t mAbsY;
+    int32_t mAbsPressure;
+    int32_t mAbsToolWidth;
+    int32_t mAbsDistance;
+    int32_t mAbsTiltX;
+    int32_t mAbsTiltY;
+
+    void clearAbsoluteAxes();
+};
+
+/* Keeps track of the state of multi-touch protocol. */
+class MultiTouchMotionAccumulator {
+public:
+    class Slot {
+    public:
+        inline bool isInUse() const { return mInUse; }
+        inline int32_t getX() const { return mAbsMTPositionX; }
+        inline int32_t getY() const { return mAbsMTPositionY; }
+        inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; }
+        inline int32_t getTouchMinor() const {
+            return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor;
+        }
+        inline int32_t getToolMajor() const { return mAbsMTWidthMajor; }
+        inline int32_t getToolMinor() const {
+            return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor;
+        }
+        inline int32_t getOrientation() const { return mAbsMTOrientation; }
+        inline int32_t getTrackingId() const { return mAbsMTTrackingId; }
+        inline int32_t getPressure() const { return mAbsMTPressure; }
+        inline int32_t getDistance() const { return mAbsMTDistance; }
+        inline int32_t getToolType() const;
+
+    private:
+        friend class MultiTouchMotionAccumulator;
+
+        bool mInUse;
+        bool mHaveAbsMTTouchMinor;
+        bool mHaveAbsMTWidthMinor;
+        bool mHaveAbsMTToolType;
+
+        int32_t mAbsMTPositionX;
+        int32_t mAbsMTPositionY;
+        int32_t mAbsMTTouchMajor;
+        int32_t mAbsMTTouchMinor;
+        int32_t mAbsMTWidthMajor;
+        int32_t mAbsMTWidthMinor;
+        int32_t mAbsMTOrientation;
+        int32_t mAbsMTTrackingId;
+        int32_t mAbsMTPressure;
+        int32_t mAbsMTDistance;
+        int32_t mAbsMTToolType;
+
+        Slot();
+        void clear();
+    };
+
+    MultiTouchMotionAccumulator();
+    ~MultiTouchMotionAccumulator();
+
+    void configure(InputDevice* device, size_t slotCount, bool usingSlotsProtocol);
+    void reset(InputDevice* device);
+    void process(const RawEvent* rawEvent);
+    void finishSync();
+    bool hasStylus() const;
+
+    inline size_t getSlotCount() const { return mSlotCount; }
+    inline const Slot* getSlot(size_t index) const { return &mSlots[index]; }
+
+private:
+    int32_t mCurrentSlot;
+    Slot* mSlots;
+    size_t mSlotCount;
+    bool mUsingSlotsProtocol;
+    bool mHaveStylus;
+
+    void clearSlots(int32_t initialSlot);
+};
+
+/* An input mapper transforms raw input events into cooked event data.
+ * A single input device can have multiple associated input mappers in order to interpret
+ * different classes of events.
+ *
+ * InputMapper lifecycle:
+ * - create
+ * - configure with 0 changes
+ * - reset
+ * - process, process, process (may occasionally reconfigure with non-zero changes or reset)
+ * - reset
+ * - destroy
+ */
+class InputMapper {
+public:
+    explicit InputMapper(InputDevice* device);
+    virtual ~InputMapper();
+
+    inline InputDevice* getDevice() { return mDevice; }
+    inline int32_t getDeviceId() { return mDevice->getId(); }
+    inline const std::string getDeviceName() { return mDevice->getName(); }
+    inline InputReaderContext* getContext() { return mContext; }
+    inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); }
+    inline InputListenerInterface* getListener() { return mContext->getListener(); }
+    inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+    virtual uint32_t getSources() = 0;
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(std::string& dump);
+    virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent) = 0;
+    virtual void timeoutExpired(nsecs_t when);
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+                                       const int32_t* keyCodes, uint8_t* outFlags);
+    virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+    virtual void cancelVibrate(int32_t token);
+    virtual void cancelTouch(nsecs_t when);
+
+    virtual int32_t getMetaState();
+    virtual void updateMetaState(int32_t keyCode);
+
+    virtual void updateExternalStylusState(const StylusState& state);
+
+    virtual void fadePointer();
+    virtual std::optional<int32_t> getAssociatedDisplayId() { return std::nullopt; }
+
+protected:
+    InputDevice* mDevice;
+    InputReaderContext* mContext;
+
+    status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+    void bumpGeneration();
+
+    static void dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
+                                        const char* name);
+    static void dumpStylusState(std::string& dump, const StylusState& state);
+};
+
+class SwitchInputMapper : public InputMapper {
+public:
+    explicit SwitchInputMapper(InputDevice* device);
+    virtual ~SwitchInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    virtual void dump(std::string& dump);
+
+private:
+    uint32_t mSwitchValues;
+    uint32_t mUpdatedSwitchMask;
+
+    void processSwitch(int32_t switchCode, int32_t switchValue);
+    void sync(nsecs_t when);
+};
+
+class VibratorInputMapper : public InputMapper {
+public:
+    explicit VibratorInputMapper(InputDevice* device);
+    virtual ~VibratorInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+    virtual void cancelVibrate(int32_t token);
+    virtual void timeoutExpired(nsecs_t when);
+    virtual void dump(std::string& dump);
+
+private:
+    bool mVibrating;
+    nsecs_t mPattern[MAX_VIBRATE_PATTERN_SIZE];
+    size_t mPatternSize;
+    ssize_t mRepeat;
+    int32_t mToken;
+    ssize_t mIndex;
+    nsecs_t mNextStepTime;
+
+    void nextStep();
+    void stopVibrating();
+};
+
+class KeyboardInputMapper : public InputMapper {
+public:
+    KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType);
+    virtual ~KeyboardInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(std::string& dump);
+    virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+                                       const int32_t* keyCodes, uint8_t* outFlags);
+
+    virtual int32_t getMetaState();
+    virtual void updateMetaState(int32_t keyCode);
+    virtual std::optional<int32_t> getAssociatedDisplayId();
+
+private:
+    // The current viewport.
+    std::optional<DisplayViewport> mViewport;
+
+    struct KeyDown {
+        int32_t keyCode;
+        int32_t scanCode;
+    };
+
+    uint32_t mSource;
+    int32_t mKeyboardType;
+
+    std::vector<KeyDown> mKeyDowns; // keys that are down
+    int32_t mMetaState;
+    nsecs_t mDownTime; // time of most recent key down
+
+    int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none
+
+    struct LedState {
+        bool avail; // led is available
+        bool on;    // we think the led is currently on
+    };
+    LedState mCapsLockLedState;
+    LedState mNumLockLedState;
+    LedState mScrollLockLedState;
+
+    // Immutable configuration parameters.
+    struct Parameters {
+        bool orientationAware;
+        bool handlesKeyRepeat;
+    } mParameters;
+
+    void configureParameters();
+    void dumpParameters(std::string& dump);
+
+    int32_t getOrientation();
+    int32_t getDisplayId();
+
+    bool isKeyboardOrGamepadKey(int32_t scanCode);
+    bool isMediaKey(int32_t keyCode);
+
+    void processKey(nsecs_t when, bool down, int32_t scanCode, int32_t usageCode);
+
+    bool updateMetaStateIfNeeded(int32_t keyCode, bool down);
+
+    ssize_t findKeyDown(int32_t scanCode);
+
+    void resetLedState();
+    void initializeLedState(LedState& ledState, int32_t led);
+    void updateLedState(bool reset);
+    void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset);
+    std::optional<DisplayViewport> findViewport(nsecs_t when,
+                                                const InputReaderConfiguration* config);
+};
+
+class CursorInputMapper : public InputMapper {
+public:
+    explicit CursorInputMapper(InputDevice* device);
+    virtual ~CursorInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(std::string& dump);
+    virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+
+    virtual void fadePointer();
+
+    virtual std::optional<int32_t> getAssociatedDisplayId();
+
+private:
+    // Amount that trackball needs to move in order to generate a key event.
+    static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+    // Immutable configuration parameters.
+    struct Parameters {
+        enum Mode {
+            MODE_POINTER,
+            MODE_POINTER_RELATIVE,
+            MODE_NAVIGATION,
+        };
+
+        Mode mode;
+        bool hasAssociatedDisplay;
+        bool orientationAware;
+    } mParameters;
+
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    CursorMotionAccumulator mCursorMotionAccumulator;
+    CursorScrollAccumulator mCursorScrollAccumulator;
+
+    int32_t mSource;
+    float mXScale;
+    float mYScale;
+    float mXPrecision;
+    float mYPrecision;
+
+    float mVWheelScale;
+    float mHWheelScale;
+
+    // Velocity controls for mouse pointer and wheel movements.
+    // The controls for X and Y wheel movements are separate to keep them decoupled.
+    VelocityControl mPointerVelocityControl;
+    VelocityControl mWheelXVelocityControl;
+    VelocityControl mWheelYVelocityControl;
+
+    int32_t mOrientation;
+
+    sp<PointerControllerInterface> mPointerController;
+
+    int32_t mButtonState;
+    nsecs_t mDownTime;
+
+    void configureParameters();
+    void dumpParameters(std::string& dump);
+
+    void sync(nsecs_t when);
+};
+
+class RotaryEncoderInputMapper : public InputMapper {
+public:
+    explicit RotaryEncoderInputMapper(InputDevice* device);
+    virtual ~RotaryEncoderInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(std::string& dump);
+    virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+
+private:
+    CursorScrollAccumulator mRotaryEncoderScrollAccumulator;
+
+    int32_t mSource;
+    float mScalingFactor;
+    int32_t mOrientation;
+
+    void sync(nsecs_t when);
+};
+
+class TouchInputMapper : public InputMapper {
+public:
+    explicit TouchInputMapper(InputDevice* device);
+    virtual ~TouchInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(std::string& dump);
+    virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+                                       const int32_t* keyCodes, uint8_t* outFlags);
+
+    virtual void fadePointer();
+    virtual void cancelTouch(nsecs_t when);
+    virtual void timeoutExpired(nsecs_t when);
+    virtual void updateExternalStylusState(const StylusState& state);
+    virtual std::optional<int32_t> getAssociatedDisplayId();
+
+protected:
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    CursorScrollAccumulator mCursorScrollAccumulator;
+    TouchButtonAccumulator mTouchButtonAccumulator;
+
+    struct VirtualKey {
+        int32_t keyCode;
+        int32_t scanCode;
+        uint32_t flags;
+
+        // computed hit box, specified in touch screen coords based on known display size
+        int32_t hitLeft;
+        int32_t hitTop;
+        int32_t hitRight;
+        int32_t hitBottom;
+
+        inline bool isHit(int32_t x, int32_t y) const {
+            return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
+        }
+    };
+
+    // Input sources and device mode.
+    uint32_t mSource;
+
+    enum DeviceMode {
+        DEVICE_MODE_DISABLED,   // input is disabled
+        DEVICE_MODE_DIRECT,     // direct mapping (touchscreen)
+        DEVICE_MODE_UNSCALED,   // unscaled mapping (touchpad)
+        DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
+        DEVICE_MODE_POINTER,    // pointer mapping (pointer)
+    };
+    DeviceMode mDeviceMode;
+
+    // The reader's configuration.
+    InputReaderConfiguration mConfig;
+
+    // Immutable configuration parameters.
+    struct Parameters {
+        enum DeviceType {
+            DEVICE_TYPE_TOUCH_SCREEN,
+            DEVICE_TYPE_TOUCH_PAD,
+            DEVICE_TYPE_TOUCH_NAVIGATION,
+            DEVICE_TYPE_POINTER,
+        };
+
+        DeviceType deviceType;
+        bool hasAssociatedDisplay;
+        bool associatedDisplayIsExternal;
+        bool orientationAware;
+        bool hasButtonUnderPad;
+        std::string uniqueDisplayId;
+
+        enum GestureMode {
+            GESTURE_MODE_SINGLE_TOUCH,
+            GESTURE_MODE_MULTI_TOUCH,
+        };
+        GestureMode gestureMode;
+
+        bool wake;
+    } mParameters;
+
+    // Immutable calibration parameters in parsed form.
+    struct Calibration {
+        // Size
+        enum SizeCalibration {
+            SIZE_CALIBRATION_DEFAULT,
+            SIZE_CALIBRATION_NONE,
+            SIZE_CALIBRATION_GEOMETRIC,
+            SIZE_CALIBRATION_DIAMETER,
+            SIZE_CALIBRATION_BOX,
+            SIZE_CALIBRATION_AREA,
+        };
+
+        SizeCalibration sizeCalibration;
+
+        bool haveSizeScale;
+        float sizeScale;
+        bool haveSizeBias;
+        float sizeBias;
+        bool haveSizeIsSummed;
+        bool sizeIsSummed;
+
+        // Pressure
+        enum PressureCalibration {
+            PRESSURE_CALIBRATION_DEFAULT,
+            PRESSURE_CALIBRATION_NONE,
+            PRESSURE_CALIBRATION_PHYSICAL,
+            PRESSURE_CALIBRATION_AMPLITUDE,
+        };
+
+        PressureCalibration pressureCalibration;
+        bool havePressureScale;
+        float pressureScale;
+
+        // Orientation
+        enum OrientationCalibration {
+            ORIENTATION_CALIBRATION_DEFAULT,
+            ORIENTATION_CALIBRATION_NONE,
+            ORIENTATION_CALIBRATION_INTERPOLATED,
+            ORIENTATION_CALIBRATION_VECTOR,
+        };
+
+        OrientationCalibration orientationCalibration;
+
+        // Distance
+        enum DistanceCalibration {
+            DISTANCE_CALIBRATION_DEFAULT,
+            DISTANCE_CALIBRATION_NONE,
+            DISTANCE_CALIBRATION_SCALED,
+        };
+
+        DistanceCalibration distanceCalibration;
+        bool haveDistanceScale;
+        float distanceScale;
+
+        enum CoverageCalibration {
+            COVERAGE_CALIBRATION_DEFAULT,
+            COVERAGE_CALIBRATION_NONE,
+            COVERAGE_CALIBRATION_BOX,
+        };
+
+        CoverageCalibration coverageCalibration;
+
+        inline void applySizeScaleAndBias(float* outSize) const {
+            if (haveSizeScale) {
+                *outSize *= sizeScale;
+            }
+            if (haveSizeBias) {
+                *outSize += sizeBias;
+            }
+            if (*outSize < 0) {
+                *outSize = 0;
+            }
+        }
+    } mCalibration;
+
+    // Affine location transformation/calibration
+    struct TouchAffineTransformation mAffineTransform;
+
+    RawPointerAxes mRawPointerAxes;
+
+    struct RawState {
+        nsecs_t when;
+
+        // Raw pointer sample data.
+        RawPointerData rawPointerData;
+
+        int32_t buttonState;
+
+        // Scroll state.
+        int32_t rawVScroll;
+        int32_t rawHScroll;
+
+        void copyFrom(const RawState& other) {
+            when = other.when;
+            rawPointerData.copyFrom(other.rawPointerData);
+            buttonState = other.buttonState;
+            rawVScroll = other.rawVScroll;
+            rawHScroll = other.rawHScroll;
+        }
+
+        void clear() {
+            when = 0;
+            rawPointerData.clear();
+            buttonState = 0;
+            rawVScroll = 0;
+            rawHScroll = 0;
+        }
+    };
+
+    struct CookedState {
+        // Cooked pointer sample data.
+        CookedPointerData cookedPointerData;
+
+        // Id bits used to differentiate fingers, stylus and mouse tools.
+        BitSet32 fingerIdBits;
+        BitSet32 stylusIdBits;
+        BitSet32 mouseIdBits;
+
+        int32_t buttonState;
+
+        void copyFrom(const CookedState& other) {
+            cookedPointerData.copyFrom(other.cookedPointerData);
+            fingerIdBits = other.fingerIdBits;
+            stylusIdBits = other.stylusIdBits;
+            mouseIdBits = other.mouseIdBits;
+            buttonState = other.buttonState;
+        }
+
+        void clear() {
+            cookedPointerData.clear();
+            fingerIdBits.clear();
+            stylusIdBits.clear();
+            mouseIdBits.clear();
+            buttonState = 0;
+        }
+    };
+
+    std::vector<RawState> mRawStatesPending;
+    RawState mCurrentRawState;
+    CookedState mCurrentCookedState;
+    RawState mLastRawState;
+    CookedState mLastCookedState;
+
+    // State provided by an external stylus
+    StylusState mExternalStylusState;
+    int64_t mExternalStylusId;
+    nsecs_t mExternalStylusFusionTimeout;
+    bool mExternalStylusDataPending;
+
+    // True if we sent a HOVER_ENTER event.
+    bool mSentHoverEnter;
+
+    // Have we assigned pointer IDs for this stream
+    bool mHavePointerIds;
+
+    // Is the current stream of direct touch events aborted
+    bool mCurrentMotionAborted;
+
+    // The time the primary pointer last went down.
+    nsecs_t mDownTime;
+
+    // The pointer controller, or null if the device is not a pointer.
+    sp<PointerControllerInterface> mPointerController;
+
+    std::vector<VirtualKey> mVirtualKeys;
+
+    virtual void configureParameters();
+    virtual void dumpParameters(std::string& dump);
+    virtual void configureRawPointerAxes();
+    virtual void dumpRawPointerAxes(std::string& dump);
+    virtual void configureSurface(nsecs_t when, bool* outResetNeeded);
+    virtual void dumpSurface(std::string& dump);
+    virtual void configureVirtualKeys();
+    virtual void dumpVirtualKeys(std::string& dump);
+    virtual void parseCalibration();
+    virtual void resolveCalibration();
+    virtual void dumpCalibration(std::string& dump);
+    virtual void updateAffineTransformation();
+    virtual void dumpAffineTransformation(std::string& dump);
+    virtual void resolveExternalStylusPresence();
+    virtual bool hasStylus() const = 0;
+    virtual bool hasExternalStylus() const;
+
+    virtual void syncTouch(nsecs_t when, RawState* outState) = 0;
+
+private:
+    // The current viewport.
+    // The components of the viewport are specified in the display's rotated orientation.
+    DisplayViewport mViewport;
+
+    // The surface orientation, width and height set by configureSurface().
+    // The width and height are derived from the viewport but are specified
+    // in the natural orientation.
+    // The surface origin specifies how the surface coordinates should be translated
+    // to align with the logical display coordinate space.
+    int32_t mSurfaceWidth;
+    int32_t mSurfaceHeight;
+    int32_t mSurfaceLeft;
+    int32_t mSurfaceTop;
+
+    // Similar to the surface coordinates, but in the raw display coordinate space rather than in
+    // the logical coordinate space.
+    int32_t mPhysicalWidth;
+    int32_t mPhysicalHeight;
+    int32_t mPhysicalLeft;
+    int32_t mPhysicalTop;
+
+    // The orientation may be different from the viewport orientation as it specifies
+    // the rotation of the surface coordinates required to produce the viewport's
+    // requested orientation, so it will depend on whether the device is orientation aware.
+    int32_t mSurfaceOrientation;
+
+    // Translation and scaling factors, orientation-independent.
+    float mXTranslate;
+    float mXScale;
+    float mXPrecision;
+
+    float mYTranslate;
+    float mYScale;
+    float mYPrecision;
+
+    float mGeometricScale;
+
+    float mPressureScale;
+
+    float mSizeScale;
+
+    float mOrientationScale;
+
+    float mDistanceScale;
+
+    bool mHaveTilt;
+    float mTiltXCenter;
+    float mTiltXScale;
+    float mTiltYCenter;
+    float mTiltYScale;
+
+    bool mExternalStylusConnected;
+
+    // Oriented motion ranges for input device info.
+    struct OrientedRanges {
+        InputDeviceInfo::MotionRange x;
+        InputDeviceInfo::MotionRange y;
+        InputDeviceInfo::MotionRange pressure;
+
+        bool haveSize;
+        InputDeviceInfo::MotionRange size;
+
+        bool haveTouchSize;
+        InputDeviceInfo::MotionRange touchMajor;
+        InputDeviceInfo::MotionRange touchMinor;
+
+        bool haveToolSize;
+        InputDeviceInfo::MotionRange toolMajor;
+        InputDeviceInfo::MotionRange toolMinor;
+
+        bool haveOrientation;
+        InputDeviceInfo::MotionRange orientation;
+
+        bool haveDistance;
+        InputDeviceInfo::MotionRange distance;
+
+        bool haveTilt;
+        InputDeviceInfo::MotionRange tilt;
+
+        OrientedRanges() { clear(); }
+
+        void clear() {
+            haveSize = false;
+            haveTouchSize = false;
+            haveToolSize = false;
+            haveOrientation = false;
+            haveDistance = false;
+            haveTilt = false;
+        }
+    } mOrientedRanges;
+
+    // Oriented dimensions and precision.
+    float mOrientedXPrecision;
+    float mOrientedYPrecision;
+
+    struct CurrentVirtualKeyState {
+        bool down;
+        bool ignored;
+        nsecs_t downTime;
+        int32_t keyCode;
+        int32_t scanCode;
+    } mCurrentVirtualKey;
+
+    // Scale factor for gesture or mouse based pointer movements.
+    float mPointerXMovementScale;
+    float mPointerYMovementScale;
+
+    // Scale factor for gesture based zooming and other freeform motions.
+    float mPointerXZoomScale;
+    float mPointerYZoomScale;
+
+    // The maximum swipe width.
+    float mPointerGestureMaxSwipeWidth;
+
+    struct PointerDistanceHeapElement {
+        uint32_t currentPointerIndex : 8;
+        uint32_t lastPointerIndex : 8;
+        uint64_t distance : 48; // squared distance
+    };
+
+    enum PointerUsage {
+        POINTER_USAGE_NONE,
+        POINTER_USAGE_GESTURES,
+        POINTER_USAGE_STYLUS,
+        POINTER_USAGE_MOUSE,
+    };
+    PointerUsage mPointerUsage;
+
+    struct PointerGesture {
+        enum Mode {
+            // No fingers, button is not pressed.
+            // Nothing happening.
+            NEUTRAL,
+
+            // No fingers, button is not pressed.
+            // Tap detected.
+            // Emits DOWN and UP events at the pointer location.
+            TAP,
+
+            // Exactly one finger dragging following a tap.
+            // Pointer follows the active finger.
+            // Emits DOWN, MOVE and UP events at the pointer location.
+            //
+            // Detect double-taps when the finger goes up while in TAP_DRAG mode.
+            TAP_DRAG,
+
+            // Button is pressed.
+            // Pointer follows the active finger if there is one.  Other fingers are ignored.
+            // Emits DOWN, MOVE and UP events at the pointer location.
+            BUTTON_CLICK_OR_DRAG,
+
+            // Exactly one finger, button is not pressed.
+            // Pointer follows the active finger.
+            // Emits HOVER_MOVE events at the pointer location.
+            //
+            // Detect taps when the finger goes up while in HOVER mode.
+            HOVER,
+
+            // Exactly two fingers but neither have moved enough to clearly indicate
+            // whether a swipe or freeform gesture was intended.  We consider the
+            // pointer to be pressed so this enables clicking or long-pressing on buttons.
+            // Pointer does not move.
+            // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+            PRESS,
+
+            // Exactly two fingers moving in the same direction, button is not pressed.
+            // Pointer does not move.
+            // Emits DOWN, MOVE and UP events with a single pointer coordinate that
+            // follows the midpoint between both fingers.
+            SWIPE,
+
+            // Two or more fingers moving in arbitrary directions, button is not pressed.
+            // Pointer does not move.
+            // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
+            // each finger individually relative to the initial centroid of the finger.
+            FREEFORM,
+
+            // Waiting for quiet time to end before starting the next gesture.
+            QUIET,
+        };
+
+        // Time the first finger went down.
+        nsecs_t firstTouchTime;
+
+        // The active pointer id from the raw touch data.
+        int32_t activeTouchId; // -1 if none
+
+        // The active pointer id from the gesture last delivered to the application.
+        int32_t activeGestureId; // -1 if none
+
+        // Pointer coords and ids for the current and previous pointer gesture.
+        Mode currentGestureMode;
+        BitSet32 currentGestureIdBits;
+        uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
+        PointerProperties currentGestureProperties[MAX_POINTERS];
+        PointerCoords currentGestureCoords[MAX_POINTERS];
+
+        Mode lastGestureMode;
+        BitSet32 lastGestureIdBits;
+        uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
+        PointerProperties lastGestureProperties[MAX_POINTERS];
+        PointerCoords lastGestureCoords[MAX_POINTERS];
+
+        // Time the pointer gesture last went down.
+        nsecs_t downTime;
+
+        // Time when the pointer went down for a TAP.
+        nsecs_t tapDownTime;
+
+        // Time when the pointer went up for a TAP.
+        nsecs_t tapUpTime;
+
+        // Location of initial tap.
+        float tapX, tapY;
+
+        // Time we started waiting for quiescence.
+        nsecs_t quietTime;
+
+        // Reference points for multitouch gestures.
+        float referenceTouchX; // reference touch X/Y coordinates in surface units
+        float referenceTouchY;
+        float referenceGestureX; // reference gesture X/Y coordinates in pixels
+        float referenceGestureY;
+
+        // Distance that each pointer has traveled which has not yet been
+        // subsumed into the reference gesture position.
+        BitSet32 referenceIdBits;
+        struct Delta {
+            float dx, dy;
+        };
+        Delta referenceDeltas[MAX_POINTER_ID + 1];
+
+        // Describes how touch ids are mapped to gesture ids for freeform gestures.
+        uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
+        // A velocity tracker for determining whether to switch active pointers during drags.
+        VelocityTracker velocityTracker;
+
+        void reset() {
+            firstTouchTime = LLONG_MIN;
+            activeTouchId = -1;
+            activeGestureId = -1;
+            currentGestureMode = NEUTRAL;
+            currentGestureIdBits.clear();
+            lastGestureMode = NEUTRAL;
+            lastGestureIdBits.clear();
+            downTime = 0;
+            velocityTracker.clear();
+            resetTap();
+            resetQuietTime();
+        }
+
+        void resetTap() {
+            tapDownTime = LLONG_MIN;
+            tapUpTime = LLONG_MIN;
+        }
+
+        void resetQuietTime() { quietTime = LLONG_MIN; }
+    } mPointerGesture;
+
+    struct PointerSimple {
+        PointerCoords currentCoords;
+        PointerProperties currentProperties;
+        PointerCoords lastCoords;
+        PointerProperties lastProperties;
+
+        // True if the pointer is down.
+        bool down;
+
+        // True if the pointer is hovering.
+        bool hovering;
+
+        // Time the pointer last went down.
+        nsecs_t downTime;
+
+        void reset() {
+            currentCoords.clear();
+            currentProperties.clear();
+            lastCoords.clear();
+            lastProperties.clear();
+            down = false;
+            hovering = false;
+            downTime = 0;
+        }
+    } mPointerSimple;
+
+    // The pointer and scroll velocity controls.
+    VelocityControl mPointerVelocityControl;
+    VelocityControl mWheelXVelocityControl;
+    VelocityControl mWheelYVelocityControl;
+
+    std::optional<DisplayViewport> findViewport();
+
+    void resetExternalStylus();
+    void clearStylusDataPendingFlags();
+
+    void sync(nsecs_t when);
+
+    bool consumeRawTouches(nsecs_t when, uint32_t policyFlags);
+    void processRawTouches(bool timeout);
+    void cookAndDispatch(nsecs_t when);
+    void dispatchVirtualKey(nsecs_t when, uint32_t policyFlags, int32_t keyEventAction,
+                            int32_t keyEventFlags);
+
+    void dispatchTouches(nsecs_t when, uint32_t policyFlags);
+    void dispatchHoverExit(nsecs_t when, uint32_t policyFlags);
+    void dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags);
+    void dispatchButtonRelease(nsecs_t when, uint32_t policyFlags);
+    void dispatchButtonPress(nsecs_t when, uint32_t policyFlags);
+    const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData);
+    void cookPointerData();
+    void abortTouches(nsecs_t when, uint32_t policyFlags);
+
+    void dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, PointerUsage pointerUsage);
+    void abortPointerUsage(nsecs_t when, uint32_t policyFlags);
+
+    void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
+    void abortPointerGestures(nsecs_t when, uint32_t policyFlags);
+    bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
+                                bool* outFinishPreviousGesture, bool isTimeout);
+
+    void dispatchPointerStylus(nsecs_t when, uint32_t policyFlags);
+    void abortPointerStylus(nsecs_t when, uint32_t policyFlags);
+
+    void dispatchPointerMouse(nsecs_t when, uint32_t policyFlags);
+    void abortPointerMouse(nsecs_t when, uint32_t policyFlags);
+
+    void dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, bool down, bool hovering);
+    void abortPointerSimple(nsecs_t when, uint32_t policyFlags);
+
+    bool assignExternalStylusId(const RawState& state, bool timeout);
+    void applyExternalStylusButtonState(nsecs_t when);
+    void applyExternalStylusTouchState(nsecs_t when);
+
+    // Dispatches a motion event.
+    // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
+    // method will take care of setting the index and transmuting the action to DOWN or UP
+    // it is the first / last pointer to go down / up.
+    void dispatchMotion(nsecs_t when, 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 PointerProperties* properties,
+                        const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits,
+                        int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime);
+
+    // Updates pointer coords and properties for pointers with specified ids that have moved.
+    // Returns true if any of them changed.
+    bool updateMovedPointers(const PointerProperties* inProperties, const PointerCoords* inCoords,
+                             const uint32_t* inIdToIndex, PointerProperties* outProperties,
+                             PointerCoords* outCoords, const uint32_t* outIdToIndex,
+                             BitSet32 idBits) const;
+
+    bool isPointInsideSurface(int32_t x, int32_t y);
+    const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
+
+    static void assignPointerIds(const RawState* last, RawState* current);
+
+    const char* modeToString(DeviceMode deviceMode);
+};
+
+class SingleTouchInputMapper : public TouchInputMapper {
+public:
+    explicit SingleTouchInputMapper(InputDevice* device);
+    virtual ~SingleTouchInputMapper();
+
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+
+protected:
+    virtual void syncTouch(nsecs_t when, RawState* outState);
+    virtual void configureRawPointerAxes();
+    virtual bool hasStylus() const;
+
+private:
+    SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+};
+
+class MultiTouchInputMapper : public TouchInputMapper {
+public:
+    explicit MultiTouchInputMapper(InputDevice* device);
+    virtual ~MultiTouchInputMapper();
+
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+
+protected:
+    virtual void syncTouch(nsecs_t when, RawState* outState);
+    virtual void configureRawPointerAxes();
+    virtual bool hasStylus() const;
+
+private:
+    MultiTouchMotionAccumulator mMultiTouchMotionAccumulator;
+
+    // Specifies the pointer id bits that are in use, and their associated tracking id.
+    BitSet32 mPointerIdBits;
+    int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+};
+
+class ExternalStylusInputMapper : public InputMapper {
+public:
+    explicit ExternalStylusInputMapper(InputDevice* device);
+    virtual ~ExternalStylusInputMapper() = default;
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(std::string& dump);
+    virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+    virtual void sync(nsecs_t when);
+
+private:
+    SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+    RawAbsoluteAxisInfo mRawPressureAxis;
+    TouchButtonAccumulator mTouchButtonAccumulator;
+
+    StylusState mStylusState;
+};
+
+class JoystickInputMapper : public InputMapper {
+public:
+    explicit JoystickInputMapper(InputDevice* device);
+    virtual ~JoystickInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(std::string& dump);
+    virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+    virtual void reset(nsecs_t when);
+    virtual void process(const RawEvent* rawEvent);
+
+private:
+    struct Axis {
+        RawAbsoluteAxisInfo rawAxisInfo;
+        AxisInfo axisInfo;
+
+        bool explicitlyMapped; // true if the axis was explicitly assigned an axis id
+
+        float scale;      // scale factor from raw to normalized values
+        float offset;     // offset to add after scaling for normalization
+        float highScale;  // scale factor from raw to normalized values of high split
+        float highOffset; // offset to add after scaling for normalization of high split
+
+        float min;        // normalized inclusive minimum
+        float max;        // normalized inclusive maximum
+        float flat;       // normalized flat region size
+        float fuzz;       // normalized error tolerance
+        float resolution; // normalized resolution in units/mm
+
+        float filter;           // filter out small variations of this size
+        float currentValue;     // current value
+        float newValue;         // most recent value
+        float highCurrentValue; // current value of high split
+        float highNewValue;     // most recent value of high split
+
+        void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo,
+                        bool explicitlyMapped, float scale, float offset, float highScale,
+                        float highOffset, float min, float max, float flat, float fuzz,
+                        float resolution) {
+            this->rawAxisInfo = rawAxisInfo;
+            this->axisInfo = axisInfo;
+            this->explicitlyMapped = explicitlyMapped;
+            this->scale = scale;
+            this->offset = offset;
+            this->highScale = highScale;
+            this->highOffset = highOffset;
+            this->min = min;
+            this->max = max;
+            this->flat = flat;
+            this->fuzz = fuzz;
+            this->resolution = resolution;
+            this->filter = 0;
+            resetValue();
+        }
+
+        void resetValue() {
+            this->currentValue = 0;
+            this->newValue = 0;
+            this->highCurrentValue = 0;
+            this->highNewValue = 0;
+        }
+    };
+
+    // Axes indexed by raw ABS_* axis index.
+    KeyedVector<int32_t, Axis> mAxes;
+
+    void sync(nsecs_t when, bool force);
+
+    bool haveAxis(int32_t axisId);
+    void pruneAxes(bool ignoreExplicitlyMappedAxes);
+    bool filterAxes(bool force);
+
+    static bool hasValueChangedSignificantly(float filter, float newValue, float currentValue,
+                                             float min, float max);
+    static bool hasMovedNearerToValueWithinFilteredRange(float filter, float newValue,
+                                                         float currentValue, float thresholdValue);
+
+    static bool isCenteredAxis(int32_t axis);
+    static int32_t getCompatAxis(int32_t axis);
+
+    static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info);
+    static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis, float value);
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_READER_H
diff --git a/services/inputflinger/reader/include/TouchVideoDevice.h b/services/inputflinger/reader/include/TouchVideoDevice.h
new file mode 100644
index 0000000..9dfa951
--- /dev/null
+++ b/services/inputflinger/reader/include/TouchVideoDevice.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _INPUTFLINGER_TOUCH_VIDEO_DEVICE_H
+#define _INPUTFLINGER_TOUCH_VIDEO_DEVICE_H
+
+#include <android-base/unique_fd.h>
+#include <input/TouchVideoFrame.h>
+#include <stdint.h>
+#include <array>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace android {
+
+/**
+ * Represents a video device that uses v4l2 api to report touch heatmap data.
+ */
+class TouchVideoDevice {
+public:
+    /**
+     * Create a new TouchVideoDevice for the path provided.
+     * Return nullptr upon failure.
+     */
+    static std::unique_ptr<TouchVideoDevice> create(std::string devicePath);
+    ~TouchVideoDevice();
+
+    bool hasValidFd() const { return mFd.get() != INVALID_FD; }
+    /**
+     * Obtain the file descriptor associated with this video device.
+     * Could be used for adding to epoll.
+     */
+    int getFd() const { return mFd.get(); }
+    /**
+     * Get the name of this video device.
+     */
+    const std::string& getName() const { return mName; }
+    /**
+     * Get the file path of this video device.
+     */
+    const std::string& getPath() const { return mPath; }
+    /**
+     * Get the height of the heatmap frame
+     */
+    uint32_t getHeight() const { return mHeight; }
+    /**
+     * Get the width of the heatmap frame
+     */
+    uint32_t getWidth() const { return mWidth; }
+    /**
+     * Direct read of the frame. Stores the frame into internal buffer.
+     * Return the number of frames that were successfully read.
+     *
+     * This function should not be called unless buffer is ready!
+     * This must be checked with select, poll, epoll, or similar api first.
+     * If epoll indicates that there is data ready to read, but this function
+     * returns zero, then it is likely an error occurred.
+     */
+    size_t readAndQueueFrames();
+    /**
+     * Return all of the queued frames, and erase them from the local buffer.
+     * The returned frames are in the order that they were received from the
+     * v4l2 device, with the oldest frame at the index 0.
+     */
+    std::vector<TouchVideoFrame> consumeFrames();
+    /**
+     * Get string representation of this video device.
+     */
+    std::string dump() const;
+
+private:
+    android::base::unique_fd mFd;
+    std::string mName;
+    std::string mPath;
+
+    uint32_t mHeight;
+    uint32_t mWidth;
+
+    static constexpr int INVALID_FD = -1;
+    /**
+     * How many buffers to request for heatmap.
+     * The kernel driver will be allocating these buffers for us,
+     * and will provide memory locations to read these from.
+     */
+    static constexpr size_t NUM_BUFFERS = 3;
+    std::array<const int16_t*, NUM_BUFFERS> mReadLocations;
+    /**
+     * How many buffers to keep for the internal queue. When the internal buffer
+     * exceeds this capacity, oldest frames will be dropped.
+     */
+    static constexpr size_t MAX_QUEUE_SIZE = 10;
+    std::vector<TouchVideoFrame> mFrames;
+
+    /**
+     * The constructor is private because opening a v4l2 device requires many checks.
+     * To get a new TouchVideoDevice, use 'create' instead.
+     */
+    explicit TouchVideoDevice(int fd, std::string&& name, std::string&& devicePath, uint32_t height,
+                              uint32_t width,
+                              const std::array<const int16_t*, NUM_BUFFERS>& readLocations);
+    /**
+     * Read all currently available frames.
+     */
+    std::vector<TouchVideoFrame> readFrames();
+    /**
+     * Read a single frame. May return nullopt if no data is currently available for reading.
+     */
+    std::optional<TouchVideoFrame> readFrame();
+};
+} // namespace android
+#endif //_INPUTFLINGER_TOUCH_VIDEO_DEVICE_H