Mouse pointer integration.

Added support for loading the pointer icon from a resource.

Moved the system server related bits of the input manager out
of libui and into libinput since they do not need to be linked into
applications.

Change-Id: Iec11e0725b3add2b905c51f8ea2c3b4b0d1a2d67
diff --git a/services/input/Android.mk b/services/input/Android.mk
new file mode 100644
index 0000000..37804ff
--- /dev/null
+++ b/services/input/Android.mk
@@ -0,0 +1,56 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+    EventHub.cpp \
+    InputDispatcher.cpp \
+    InputManager.cpp \
+    InputReader.cpp \
+    PointerController.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+    libcutils \
+    libutils \
+    libhardware \
+    libhardware_legacy \
+    libsurfaceflinger_client \
+    libskia \
+    libui
+
+LOCAL_C_INCLUDES := \
+    external/skia/include/core
+
+LOCAL_MODULE:= libinput
+
+LOCAL_MODULE_TAGS := optional
+
+ifeq ($(TARGET_SIMULATOR),true)
+    LOCAL_LDLIBS += -lpthread
+endif
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+# Include subdirectory makefiles
+# ============================================================
+
+# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
+# team really wants is to build the stuff defined by this makefile.
+ifeq (,$(ONE_SHOT_MAKEFILE))
+include $(call first-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
new file mode 100644
index 0000000..487e73f
--- /dev/null
+++ b/services/input/EventHub.cpp
@@ -0,0 +1,1095 @@
+/*
+ * 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.
+ */
+
+//
+// Handle events, like key input and vsync.
+//
+// The goal is to provide an optimized solution for Linux, not an
+// implementation that works well across all platforms.  We expect
+// events to arrive on file descriptors, so that we can use a select()
+// select() call to sleep.
+//
+// We can't select() on anything but network sockets in Windows, so we
+// provide an alternative implementation of waitEvent for that platform.
+//
+#define LOG_TAG "EventHub"
+
+//#define LOG_NDEBUG 0
+
+#include "EventHub.h"
+
+#include <hardware_legacy/power.h>
+
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <utils/threads.h>
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <ui/KeyLayoutMap.h>
+#include <ui/KeyCharacterMap.h>
+#include <ui/VirtualKeyMap.h>
+
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+#ifdef HAVE_INOTIFY
+# include <sys/inotify.h>
+#endif
+#ifdef HAVE_ANDROID_OS
+# include <sys/limits.h>        /* not part of Linux */
+#endif
+#include <sys/poll.h>
+#include <sys/ioctl.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)
+
+// Fd at index 0 is always reserved for inotify
+#define FIRST_ACTUAL_DEVICE_INDEX 1
+
+#define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+
+namespace android {
+
+static const char *WAKE_LOCK_ID = "KeyEvents";
+static const char *DEVICE_PATH = "/dev/input";
+
+/* return the larger integer */
+static inline int max(int v1, int v2)
+{
+    return (v1 > v2) ? v1 : v2;
+}
+
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+// --- EventHub::Device ---
+
+EventHub::Device::Device(int fd, int32_t id, const String8& path,
+        const InputDeviceIdentifier& identifier) :
+        next(NULL),
+        fd(fd), id(id), path(path), identifier(identifier),
+        classes(0), keyBitmask(NULL), configuration(NULL), virtualKeyMap(NULL) {
+}
+
+EventHub::Device::~Device() {
+    close();
+    delete[] keyBitmask;
+    delete configuration;
+    delete virtualKeyMap;
+}
+
+void EventHub::Device::close() {
+    if (fd >= 0) {
+        ::close(fd);
+        fd = -1;
+    }
+}
+
+
+// --- EventHub ---
+
+EventHub::EventHub(void) :
+        mError(NO_INIT), mBuiltInKeyboardId(-1), mNextDeviceId(1),
+        mOpeningDevices(0), mClosingDevices(0),
+        mOpened(false), mNeedToSendFinishedDeviceScan(false),
+        mInputBufferIndex(0), mInputBufferCount(0), mInputFdIndex(0) {
+    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+#ifdef EV_SW
+    memset(mSwitches, 0, sizeof(mSwitches));
+#endif
+}
+
+EventHub::~EventHub(void) {
+    release_wake_lock(WAKE_LOCK_ID);
+    // we should free stuff here...
+}
+
+status_t EventHub::errorCheck() const {
+    return mError;
+}
+
+String8 EventHub::getDeviceName(int32_t deviceId) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == NULL) return String8();
+    return device->identifier.name;
+}
+
+uint32_t EventHub::getDeviceClasses(int32_t deviceId) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == NULL) return 0;
+    return device->classes;
+}
+
+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();
+
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == NULL) return -1;
+
+    struct input_absinfo info;
+
+    if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+        LOGW("Error reading absolute controller %d for device %s fd %d\n",
+             axis, device->identifier.name.string(), device->fd);
+        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;
+    }
+    return OK;
+}
+
+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 != NULL) {
+            return getScanCodeStateLocked(device, scanCode);
+        }
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getScanCodeStateLocked(Device* device, int32_t scanCode) const {
+    uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)];
+    memset(key_bitmask, 0, sizeof(key_bitmask));
+    if (ioctl(device->fd,
+               EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) {
+        return test_bit(scanCode, key_bitmask) ? 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 != NULL) {
+        return getKeyCodeStateLocked(device, keyCode);
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeStateLocked(Device* device, int32_t keyCode) const {
+    if (!device->keyMap.haveKeyLayout()) {
+        return AKEY_STATE_UNKNOWN;
+    }
+
+    Vector<int32_t> scanCodes;
+    device->keyMap.keyLayoutMap->findScanCodes(keyCode, &scanCodes);
+
+    uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)];
+    memset(key_bitmask, 0, sizeof(key_bitmask));
+    if (ioctl(device->fd, EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) {
+        #if 0
+        for (size_t i=0; i<=KEY_MAX; i++) {
+            LOGI("(Scan code %d: down=%d)", i, test_bit(i, key_bitmask));
+        }
+        #endif
+        const size_t N = scanCodes.size();
+        for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+            int32_t sc = scanCodes.itemAt(i);
+            //LOGI("Code %d: down=%d", sc, test_bit(sc, key_bitmask));
+            if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, key_bitmask)) {
+                return AKEY_STATE_DOWN;
+            }
+        }
+        return AKEY_STATE_UP;
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
+#ifdef EV_SW
+    if (sw >= 0 && sw <= SW_MAX) {
+        AutoMutex _l(mLock);
+
+        Device* device = getDeviceLocked(deviceId);
+        if (device != NULL) {
+            return getSwitchStateLocked(device, sw);
+        }
+    }
+#endif
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchStateLocked(Device* device, int32_t sw) const {
+    uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)];
+    memset(sw_bitmask, 0, sizeof(sw_bitmask));
+    if (ioctl(device->fd,
+               EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) {
+        return test_bit(sw, sw_bitmask) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+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 != NULL) {
+        return markSupportedKeyCodesLocked(device, numCodes, keyCodes, outFlags);
+    }
+    return false;
+}
+
+bool EventHub::markSupportedKeyCodesLocked(Device* device, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) const {
+    if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) {
+        return false;
+    }
+
+    Vector<int32_t> scanCodes;
+    for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
+        scanCodes.clear();
+
+        status_t err = device->keyMap.keyLayoutMap->findScanCodes(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;
+}
+
+status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode,
+        int32_t* outKeycode, uint32_t* outFlags) const
+{
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    
+    if (device && device->keyMap.haveKeyLayout()) {
+        status_t err = device->keyMap.keyLayoutMap->map(scancode, outKeycode, outFlags);
+        if (err == NO_ERROR) {
+            return NO_ERROR;
+        }
+    }
+    
+    if (mBuiltInKeyboardId != -1) {
+        device = getDeviceLocked(mBuiltInKeyboardId);
+        
+        if (device && device->keyMap.haveKeyLayout()) {
+            status_t err = device->keyMap.keyLayoutMap->map(scancode, outKeycode, outFlags);
+            if (err == NO_ERROR) {
+                return NO_ERROR;
+            }
+        }
+    }
+    
+    *outKeycode = 0;
+    *outFlags = 0;
+    return NAME_NOT_FOUND;
+}
+
+void EventHub::addExcludedDevice(const char* deviceName)
+{
+    AutoMutex _l(mLock);
+
+    String8 name(deviceName);
+    mExcludedDevices.push_back(name);
+}
+
+bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device) {
+        uint8_t bitmask[sizeof_bit_array(LED_MAX + 1)];
+        memset(bitmask, 0, sizeof(bitmask));
+        if (ioctl(device->fd, EVIOCGBIT(EV_LED, sizeof(bitmask)), bitmask) >= 0) {
+            if (test_bit(led, bitmask)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device) {
+        struct input_event ev;
+        ev.time.tv_sec = 0;
+        ev.time.tv_usec = 0;
+        ev.type = EV_LED;
+        ev.code = led;
+        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,
+        Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+    outVirtualKeys.clear();
+
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device && device->virtualKeyMap) {
+        outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys());
+    }
+}
+
+EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
+    if (deviceId == 0) {
+        deviceId = mBuiltInKeyboardId;
+    }
+
+    size_t numDevices = mDevices.size();
+    for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < numDevices; i++) {
+        Device* device = mDevices[i];
+        if (device->id == deviceId) {
+            return device;
+        }
+    }
+    return NULL;
+}
+
+bool EventHub::getEvent(RawEvent* outEvent) {
+    outEvent->deviceId = 0;
+    outEvent->type = 0;
+    outEvent->scanCode = 0;
+    outEvent->keyCode = 0;
+    outEvent->flags = 0;
+    outEvent->value = 0;
+    outEvent->when = 0;
+
+    // Note that we only allow one caller to getEvent(), so don't need
+    // to do locking here...  only when adding/removing devices.
+
+    if (!mOpened) {
+        mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;
+        mOpened = true;
+        mNeedToSendFinishedDeviceScan = true;
+    }
+
+    for (;;) {
+        // Report any devices that had last been added/removed.
+        if (mClosingDevices != NULL) {
+            Device* device = mClosingDevices;
+            LOGV("Reporting device closed: id=%d, name=%s\n",
+                 device->id, device->path.string());
+            mClosingDevices = device->next;
+            if (device->id == mBuiltInKeyboardId) {
+                outEvent->deviceId = 0;
+            } else {
+                outEvent->deviceId = device->id;
+            }
+            outEvent->type = DEVICE_REMOVED;
+            outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+            delete device;
+            mNeedToSendFinishedDeviceScan = true;
+            return true;
+        }
+
+        if (mOpeningDevices != NULL) {
+            Device* device = mOpeningDevices;
+            LOGV("Reporting device opened: id=%d, name=%s\n",
+                 device->id, device->path.string());
+            mOpeningDevices = device->next;
+            if (device->id == mBuiltInKeyboardId) {
+                outEvent->deviceId = 0;
+            } else {
+                outEvent->deviceId = device->id;
+            }
+            outEvent->type = DEVICE_ADDED;
+            outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+            mNeedToSendFinishedDeviceScan = true;
+            return true;
+        }
+
+        if (mNeedToSendFinishedDeviceScan) {
+            mNeedToSendFinishedDeviceScan = false;
+            outEvent->type = FINISHED_DEVICE_SCAN;
+            outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+            return true;
+        }
+
+        // Grab the next input event.
+        for (;;) {
+            // Consume buffered input events, if any.
+            if (mInputBufferIndex < mInputBufferCount) {
+                const struct input_event& iev = mInputBufferData[mInputBufferIndex++];
+                const Device* device = mDevices[mInputFdIndex];
+
+                LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", device->path.string(),
+                     (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value);
+                if (device->id == mBuiltInKeyboardId) {
+                    outEvent->deviceId = 0;
+                } else {
+                    outEvent->deviceId = device->id;
+                }
+                outEvent->type = iev.type;
+                outEvent->scanCode = iev.code;
+                outEvent->flags = 0;
+                if (iev.type == EV_KEY) {
+                    outEvent->keyCode = AKEYCODE_UNKNOWN;
+                    if (device->keyMap.haveKeyLayout()) {
+                        status_t err = device->keyMap.keyLayoutMap->map(iev.code,
+                                &outEvent->keyCode, &outEvent->flags);
+                        LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n",
+                                iev.code, outEvent->keyCode, outEvent->flags, err);
+                    }
+                } else {
+                    outEvent->keyCode = iev.code;
+                }
+                outEvent->value = iev.value;
+
+                // Use an event timestamp in the same timebase as
+                // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis()
+                // as expected by the rest of the system.
+                outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+                return true;
+            }
+
+            // Finish reading all events from devices identified in previous poll().
+            // This code assumes that mInputDeviceIndex is initially 0 and that the
+            // revents member of pollfd is initialized to 0 when the device is first added.
+            // Since mFds[0] is used for inotify, we process regular events starting at index 1.
+            mInputFdIndex += 1;
+            if (mInputFdIndex >= mFds.size()) {
+                break;
+            }
+
+            const struct pollfd& pfd = mFds[mInputFdIndex];
+            if (pfd.revents & POLLIN) {
+                int32_t readSize = read(pfd.fd, mInputBufferData,
+                        sizeof(struct input_event) * INPUT_BUFFER_SIZE);
+                if (readSize < 0) {
+                    if (errno != EAGAIN && errno != EINTR) {
+                        LOGW("could not get event (errno=%d)", errno);
+                    }
+                } else if ((readSize % sizeof(struct input_event)) != 0) {
+                    LOGE("could not get event (wrong size: %d)", readSize);
+                } else {
+                    mInputBufferCount = size_t(readSize) / sizeof(struct input_event);
+                    mInputBufferIndex = 0;
+                }
+            }
+        }
+
+#if HAVE_INOTIFY
+        // readNotify() will modify mFDs and mFDCount, so this must be done after
+        // processing all other events.
+        if(mFds[0].revents & POLLIN) {
+            readNotify(mFds[0].fd);
+            mFds.editItemAt(0).revents = 0;
+            continue; // report added or removed devices immediately
+        }
+#endif
+
+        mInputFdIndex = 0;
+
+        // Poll for events.  Mind the wake lock dance!
+        // We hold a wake lock at all times except during poll().  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.
+        release_wake_lock(WAKE_LOCK_ID);
+
+        int pollResult = poll(mFds.editArray(), mFds.size(), -1);
+
+        acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+
+        if (pollResult <= 0) {
+            if (errno != EINTR) {
+                LOGW("poll failed (errno=%d)\n", errno);
+                usleep(100000);
+            }
+        }
+    }
+}
+
+/*
+ * Open the platform-specific input device.
+ */
+bool EventHub::openPlatformInput(void) {
+    /*
+     * Open platform-specific input device(s).
+     */
+    int res, fd;
+
+#ifdef HAVE_INOTIFY
+    fd = inotify_init();
+    res = inotify_add_watch(fd, DEVICE_PATH, IN_DELETE | IN_CREATE);
+    if(res < 0) {
+        LOGE("could not add watch for %s, %s\n", DEVICE_PATH, strerror(errno));
+    }
+#else
+    /*
+     * The code in EventHub::getEvent assumes that mFDs[0] is an inotify fd.
+     * We allocate space for it and set it to something invalid.
+     */
+    fd = -1;
+#endif
+
+    // Reserve fd index 0 for inotify.
+    struct pollfd pollfd;
+    pollfd.fd = fd;
+    pollfd.events = POLLIN;
+    pollfd.revents = 0;
+    mFds.push(pollfd);
+    mDevices.push(NULL);
+
+    res = scanDir(DEVICE_PATH);
+    if(res < 0) {
+        LOGE("scan dir failed for %s\n", DEVICE_PATH);
+    }
+
+    return true;
+}
+
+// ----------------------------------------------------------------------------
+
+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
+};
+
+int EventHub::openDevice(const char *devicePath) {
+    char buffer[80];
+
+    LOGV("Opening device: %s", devicePath);
+
+    AutoMutex _l(mLock);
+
+    int fd = open(devicePath, O_RDWR);
+    if(fd < 0) {
+        LOGE("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) {
+        //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
+    } else {
+        buffer[sizeof(buffer) - 1] = '\0';
+        identifier.name.setTo(buffer);
+    }
+
+    // Check to see if the device is on our excluded list
+    List<String8>::iterator iter = mExcludedDevices.begin();
+    List<String8>::iterator end = mExcludedDevices.end();
+    for ( ; iter != end; iter++) {
+        const char* test = *iter;
+        if (identifier.name == test) {
+            LOGI("ignoring event id %s driver %s\n", devicePath, test);
+            close(fd);
+            return -1;
+        }
+    }
+
+    // Get device driver version.
+    int driverVersion;
+    if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {
+        LOGE("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)) {
+        LOGE("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.setTo(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.setTo(buffer);
+    }
+
+    // Make file descriptor non-blocking for use with poll().
+    if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
+        LOGE("Error %d making device file descriptor non-blocking.", errno);
+        close(fd);
+        return -1;
+    }
+
+    // Allocate device.  (The device object takes ownership of the fd at this point.)
+    int32_t deviceId = mNextDeviceId++;
+    Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
+
+#if 0
+    LOGI("add device %d: %s\n", deviceId, devicePath);
+    LOGI("  bus:       %04x\n"
+         "  vendor     %04x\n"
+         "  product    %04x\n"
+         "  version    %04x\n",
+        identifier.bus, identifier.vendor, identifier.product, identifier.version);
+    LOGI("  name:      \"%s\"\n", identifier.name.string());
+    LOGI("  location:  \"%s\"\n", identifier.location.string());
+    LOGI("  unique id: \"%s\"\n", identifier.uniqueId.string());
+    LOGI("  driver:    v%d.%d.%d\n",
+        driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);
+#endif
+
+    // Load the configuration file for the device.
+    loadConfiguration(device);
+
+    // Figure out the kinds of events the device reports.
+    
+    uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)];
+    memset(key_bitmask, 0, sizeof(key_bitmask));
+
+    LOGV("Getting keys...");
+    if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) >= 0) {
+        //LOGI("MAP\n");
+        //for (int i = 0; i < sizeof(key_bitmask); i++) {
+        //    LOGI("%d: 0x%02x\n", i, key_bitmask[i]);
+        //}
+
+        // See if this is a keyboard.  Ignore everything in the button range except for
+        // gamepads which are also considered keyboards.
+        if (containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC))
+                || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_GAMEPAD),
+                        sizeof_bit_array(BTN_DIGI))
+                || containsNonZeroByte(key_bitmask, sizeof_bit_array(KEY_OK),
+                        sizeof_bit_array(KEY_MAX + 1))) {
+            device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+
+            device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
+            if (device->keyBitmask != NULL) {
+                memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
+            } else {
+                delete device;
+                LOGE("out of memory allocating key bitmask");
+                return -1;
+            }
+        }
+    }
+    
+    // See if this is a cursor device such as a trackball or mouse.
+    if (test_bit(BTN_MOUSE, key_bitmask)) {
+        uint8_t rel_bitmask[sizeof_bit_array(REL_MAX + 1)];
+        memset(rel_bitmask, 0, sizeof(rel_bitmask));
+        LOGV("Getting relative controllers...");
+        if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0) {
+            if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) {
+                device->classes |= INPUT_DEVICE_CLASS_CURSOR;
+            }
+        }
+    }
+
+    // See if this is a touch pad.
+    uint8_t abs_bitmask[sizeof_bit_array(ABS_MAX + 1)];
+    memset(abs_bitmask, 0, sizeof(abs_bitmask));
+    LOGV("Getting absolute controllers...");
+    if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0) {
+        // Is this a new modern multi-touch driver?
+        if (test_bit(ABS_MT_POSITION_X, abs_bitmask)
+                && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
+            device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT;
+
+        // Is this an old style single-touch driver?
+        } else if (test_bit(BTN_TOUCH, key_bitmask)
+                && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
+            device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN;
+        }
+    }
+
+#ifdef EV_SW
+    // figure out the switches this device reports
+    uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)];
+    memset(sw_bitmask, 0, sizeof(sw_bitmask));
+    bool hasSwitches = false;
+    if (ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask) >= 0) {
+        for (int i=0; i<EV_SW; i++) {
+            //LOGI("Device %d sw %d: has=%d", device->id, i, test_bit(i, sw_bitmask));
+            if (test_bit(i, sw_bitmask)) {
+                hasSwitches = true;
+                if (mSwitches[i] == 0) {
+                    mSwitches[i] = device->id;
+                }
+            }
+        }
+    }
+    if (hasSwitches) {
+        device->classes |= INPUT_DEVICE_CLASS_SWITCH;
+    }
+#endif
+
+    if ((device->classes & INPUT_DEVICE_CLASS_TOUCHSCREEN)) {
+        // 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.
+        status_t status = loadVirtualKeyMap(device);
+        if (!status) {
+            device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+        }
+    }
+
+    if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
+        // Load the keymap for the device.
+        status_t status = loadKeyMap(device);
+
+        // Set system properties for the keyboard.
+        setKeyboardProperties(device, false);
+
+        // Register the keyboard as a built-in keyboard if it is eligible.
+        if (!status
+                && mBuiltInKeyboardId == -1
+                && isEligibleBuiltInKeyboard(device->identifier,
+                        device->configuration, &device->keyMap)) {
+            mBuiltInKeyboardId = device->id;
+            setKeyboardProperties(device, true);
+        }
+
+        // '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) {
+        LOGV("Dropping device: id=%d, path='%s', name='%s'",
+                deviceId, devicePath, device->identifier.name.string());
+        delete device;
+        return -1;
+    }
+
+    LOGI("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.string(),
+         device->classes,
+         device->configurationFile.string(),
+         device->keyMap.keyLayoutFile.string(),
+         device->keyMap.keyCharacterMapFile.string(),
+         toString(mBuiltInKeyboardId == deviceId));
+
+    struct pollfd pollfd;
+    pollfd.fd = fd;
+    pollfd.events = POLLIN;
+    pollfd.revents = 0;
+    mFds.push(pollfd);
+    mDevices.push(device);
+
+    device->next = mOpeningDevices;
+    mOpeningDevices = device;
+    return 0;
+}
+
+void EventHub::loadConfiguration(Device* device) {
+    device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
+            device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
+    if (device->configurationFile.isEmpty()) {
+        LOGD("No input device configuration file found for device '%s'.",
+                device->identifier.name.string());
+    } else {
+        status_t status = PropertyMap::load(device->configurationFile,
+                &device->configuration);
+        if (status) {
+            LOGE("Error loading input device configuration file for device '%s'.  "
+                    "Using default configuration.",
+                    device->identifier.name.string());
+        }
+    }
+}
+
+status_t EventHub::loadVirtualKeyMap(Device* device) {
+    // The virtual key map is supplied by the kernel as a system board property file.
+    String8 path;
+    path.append("/sys/board_properties/virtualkeys.");
+    path.append(device->identifier.name);
+    if (access(path.string(), R_OK)) {
+        return NAME_NOT_FOUND;
+    }
+    return VirtualKeyMap::load(path, &device->virtualKeyMap);
+}
+
+status_t EventHub::loadKeyMap(Device* device) {
+    return device->keyMap.load(device->identifier, device->configuration);
+}
+
+void EventHub::setKeyboardProperties(Device* device, bool builtInKeyboard) {
+    int32_t id = builtInKeyboard ? 0 : device->id;
+    android::setKeyboardProperties(id, device->identifier,
+            device->keyMap.keyLayoutFile, device->keyMap.keyCharacterMapFile);
+}
+
+void EventHub::clearKeyboardProperties(Device* device, bool builtInKeyboard) {
+    int32_t id = builtInKeyboard ? 0 : device->id;
+    android::clearKeyboardProperties(id);
+}
+
+bool EventHub::hasKeycodeLocked(Device* device, int keycode) const {
+    if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) {
+        return false;
+    }
+    
+    Vector<int32_t> scanCodes;
+    device->keyMap.keyLayoutMap->findScanCodes(keycode, &scanCodes);
+    const size_t N = scanCodes.size();
+    for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+        int32_t sc = scanCodes.itemAt(i);
+        if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+            return true;
+        }
+    }
+    
+    return false;
+}
+
+int EventHub::closeDevice(const char *devicePath) {
+    AutoMutex _l(mLock);
+
+    for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < mDevices.size(); i++) {
+        Device* device = mDevices[i];
+        if (device->path == devicePath) {
+            LOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n",
+                 device->path.string(), device->identifier.name.string(), device->id,
+                 device->fd, device->classes);
+
+#ifdef EV_SW
+            for (int j=0; j<EV_SW; j++) {
+                if (mSwitches[j] == device->id) {
+                    mSwitches[j] = 0;
+                }
+            }
+#endif
+
+            if (device->id == mBuiltInKeyboardId) {
+                LOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
+                        device->path.string(), mBuiltInKeyboardId);
+                mBuiltInKeyboardId = -1;
+                clearKeyboardProperties(device, true);
+            }
+            clearKeyboardProperties(device, false);
+
+            mFds.removeAt(i);
+            mDevices.removeAt(i);
+            device->close();
+
+            device->next = mClosingDevices;
+            mClosingDevices = device;
+            return 0;
+        }
+    }
+    LOGE("remove device: %s not found\n", devicePath);
+    return -1;
+}
+
+int EventHub::readNotify(int nfd) {
+#ifdef HAVE_INOTIFY
+    int res;
+    char devname[PATH_MAX];
+    char *filename;
+    char event_buf[512];
+    int event_size;
+    int event_pos = 0;
+    struct inotify_event *event;
+
+    LOGV("EventHub::readNotify nfd: %d\n", nfd);
+    res = read(nfd, event_buf, sizeof(event_buf));
+    if(res < (int)sizeof(*event)) {
+        if(errno == EINTR)
+            return 0;
+        LOGW("could not get event, %s\n", strerror(errno));
+        return 1;
+    }
+    //printf("got %d bytes of event information\n", res);
+
+    strcpy(devname, DEVICE_PATH);
+    filename = devname + strlen(devname);
+    *filename++ = '/';
+
+    while(res >= (int)sizeof(*event)) {
+        event = (struct inotify_event *)(event_buf + event_pos);
+        //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
+        if(event->len) {
+            strcpy(filename, event->name);
+            if(event->mask & IN_CREATE) {
+                openDevice(devname);
+            }
+            else {
+                closeDevice(devname);
+            }
+        }
+        event_size = sizeof(*event) + event->len;
+        res -= event_size;
+        event_pos += event_size;
+    }
+#endif
+    return 0;
+}
+
+int EventHub::scanDir(const char *dirname)
+{
+    char devname[PATH_MAX];
+    char *filename;
+    DIR *dir;
+    struct dirent *de;
+    dir = opendir(dirname);
+    if(dir == NULL)
+        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);
+        openDevice(devname);
+    }
+    closedir(dir);
+    return 0;
+}
+
+void EventHub::dump(String8& dump) {
+    dump.append("Event Hub State:\n");
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        dump.appendFormat(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
+
+        dump.append(INDENT "Devices:\n");
+
+        for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < mDevices.size(); i++) {
+            const Device* device = mDevices[i];
+            if (device) {
+                if (mBuiltInKeyboardId == device->id) {
+                    dump.appendFormat(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n",
+                            device->id, device->identifier.name.string());
+                } else {
+                    dump.appendFormat(INDENT2 "%d: %s\n", device->id,
+                            device->identifier.name.string());
+                }
+                dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
+                dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
+                dump.appendFormat(INDENT3 "Location: %s\n", device->identifier.location.string());
+                dump.appendFormat(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.string());
+                dump.appendFormat(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.appendFormat(INDENT3 "KeyLayoutFile: %s\n",
+                        device->keyMap.keyLayoutFile.string());
+                dump.appendFormat(INDENT3 "KeyCharacterMapFile: %s\n",
+                        device->keyMap.keyCharacterMapFile.string());
+                dump.appendFormat(INDENT3 "ConfigurationFile: %s\n",
+                        device->configurationFile.string());
+            }
+        }
+    } // release lock
+}
+
+}; // namespace android
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
new file mode 100644
index 0000000..74b7ec5
--- /dev/null
+++ b/services/input/EventHub.h
@@ -0,0 +1,327 @@
+/*
+ * 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 <ui/Input.h>
+#include <ui/Keyboard.h>
+#include <ui/KeyLayoutMap.h>
+#include <ui/KeyCharacterMap.h>
+#include <ui/VirtualKeyMap.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/PropertyMap.h>
+#include <utils/Vector.h>
+
+#include <linux/input.h>
+
+/* These constants are not defined in linux/input.h but they are part of the multitouch
+ * input protocol. */
+
+#define ABS_MT_TOUCH_MAJOR 0x30  /* Major axis of touching ellipse */
+#define ABS_MT_TOUCH_MINOR 0x31  /* Minor axis (omit if circular) */
+#define ABS_MT_WIDTH_MAJOR 0x32  /* Major axis of approaching ellipse */
+#define ABS_MT_WIDTH_MINOR 0x33  /* Minor axis (omit if circular) */
+#define ABS_MT_ORIENTATION 0x34  /* Ellipse orientation */
+#define ABS_MT_POSITION_X 0x35   /* Center X ellipse position */
+#define ABS_MT_POSITION_Y 0x36   /* Center Y ellipse position */
+#define ABS_MT_TOOL_TYPE 0x37    /* Type of touching device (finger, pen, ...) */
+#define ABS_MT_BLOB_ID 0x38      /* Group a set of packets as a blob */
+#define ABS_MT_TRACKING_ID 0x39  /* Unique ID of initiated contact */
+#define ABS_MT_PRESSURE 0x3a     /* Pressure on contact area */
+
+#define MT_TOOL_FINGER 0 /* Identifies a finger */
+#define MT_TOOL_PEN 1    /* Identifies a pen */
+
+#define SYN_MT_REPORT 2
+
+/* Convenience constants. */
+
+#define BTN_FIRST 0x100  // first button scancode
+#define BTN_LAST 0x15f   // last button scancode
+
+struct pollfd;
+
+namespace android {
+
+/*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+    nsecs_t when;
+    int32_t deviceId;
+    int32_t type;
+    int32_t scanCode;
+    int32_t keyCode;
+    int32_t value;
+    uint32_t flags;
+};
+
+/* 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
+
+    inline int32_t getRange() { return maxValue - minValue; }
+
+    inline void clear() {
+        valid = false;
+        minValue = 0;
+        maxValue = 0;
+        flat = 0;
+        fuzz = 0;
+    }
+};
+
+/*
+ * Input device classes.
+ */
+enum {
+    /* The input device is a keyboard. */
+    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 (either single-touch or multi-touch). */
+    INPUT_DEVICE_CLASS_TOUCHSCREEN   = 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_TOUCHSCREEN_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,
+};
+
+/*
+ * 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 virtual RefBase {
+protected:
+    EventHubInterface() { }
+    virtual ~EventHubInterface() { }
+
+public:
+    // 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,
+    };
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
+
+    virtual String8 getDeviceName(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 status_t scancodeToKeycode(int32_t deviceId, int scancode,
+            int32_t* outKeycode, uint32_t* outFlags) const = 0;
+
+    // exclude a particular device from opening
+    // this can be used to ignore input devices for sensors
+    virtual void addExcludedDevice(const char* deviceName) = 0;
+
+    /*
+     * Wait for the next event to become available and return it.
+     * 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).
+     */
+    virtual bool getEvent(RawEvent* outEvent) = 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;
+
+    /*
+     * 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 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,
+            Vector<VirtualKeyDefinition>& outVirtualKeys) const = 0;
+
+    virtual void dump(String8& dump) = 0;
+};
+
+class EventHub : public EventHubInterface
+{
+public:
+    EventHub();
+
+    status_t errorCheck() const;
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const;
+
+    virtual String8 getDeviceName(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 status_t scancodeToKeycode(int32_t deviceId, int scancode,
+            int32_t* outKeycode, uint32_t* outFlags) const;
+
+    virtual void addExcludedDevice(const char* deviceName);
+
+    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 bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags) const;
+
+    virtual bool getEvent(RawEvent* outEvent);
+
+    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,
+            Vector<VirtualKeyDefinition>& outVirtualKeys) const;
+
+    virtual void dump(String8& dump);
+
+protected:
+    virtual ~EventHub();
+    
+private:
+    bool openPlatformInput(void);
+
+    int openDevice(const char *devicePath);
+    int closeDevice(const char *devicePath);
+    int scanDir(const char *dirname);
+    int readNotify(int nfd);
+
+    status_t mError;
+
+    struct Device {
+        Device* next;
+
+        int fd;
+        const int32_t id;
+        const String8 path;
+        const InputDeviceIdentifier identifier;
+
+        uint32_t classes;
+        uint8_t* keyBitmask;
+        String8 configurationFile;
+        PropertyMap* configuration;
+        VirtualKeyMap* virtualKeyMap;
+        KeyMap keyMap;
+
+        Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
+        ~Device();
+
+        void close();
+    };
+
+    Device* getDeviceLocked(int32_t deviceId) const;
+    bool hasKeycodeLocked(Device* device, int keycode) const;
+
+    int32_t getScanCodeStateLocked(Device* device, int32_t scanCode) const;
+    int32_t getKeyCodeStateLocked(Device* device, int32_t keyCode) const;
+    int32_t getSwitchStateLocked(Device* device, int32_t sw) const;
+    bool markSupportedKeyCodesLocked(Device* device, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags) const;
+
+    void loadConfiguration(Device* device);
+    status_t loadVirtualKeyMap(Device* device);
+    status_t loadKeyMap(Device* device);
+    void setKeyboardProperties(Device* device, bool builtInKeyboard);
+    void clearKeyboardProperties(Device* device, bool builtInKeyboard);
+
+    // Protect all internal state.
+    mutable Mutex mLock;
+
+    // The actual id of the built-in keyboard, or -1 if none.
+    // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
+    int32_t mBuiltInKeyboardId;
+
+    int32_t mNextDeviceId;
+
+    // Parallel arrays of fds and devices.
+    // First index is reserved for inotify.
+    Vector<struct pollfd> mFds;
+    Vector<Device*> mDevices;
+
+    Device *mOpeningDevices;
+    Device *mClosingDevices;
+
+    bool mOpened;
+    bool mNeedToSendFinishedDeviceScan;
+    List<String8> mExcludedDevices;
+
+    // device ids that report particular switches.
+#ifdef EV_SW
+    int32_t mSwitches[SW_MAX + 1];
+#endif
+
+    static const int INPUT_BUFFER_SIZE = 64;
+    struct input_event mInputBufferData[INPUT_BUFFER_SIZE];
+    size_t mInputBufferIndex;
+    size_t mInputBufferCount;
+    size_t mInputFdIndex;
+};
+
+}; // namespace android
+
+#endif // _RUNTIME_EVENT_HUB_H
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
new file mode 100644
index 0000000..3675021
--- /dev/null
+++ b/services/input/InputDispatcher.cpp
@@ -0,0 +1,3722 @@
+/*
+ * 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 "InputDispatcher"
+
+//#define LOG_NDEBUG 0
+
+// Log detailed debug messages about each inbound event notification to the dispatcher.
+#define DEBUG_INBOUND_EVENT_DETAILS 0
+
+// Log detailed debug messages about each outbound event processed by the dispatcher.
+#define DEBUG_OUTBOUND_EVENT_DETAILS 0
+
+// Log debug messages about batching.
+#define DEBUG_BATCHING 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 0
+
+// Log debug messages about registrations.
+#define DEBUG_REGISTRATION 0
+
+// Log debug messages about performance statistics.
+#define DEBUG_PERFORMANCE_STATISTICS 0
+
+// Log debug messages about input event injection.
+#define DEBUG_INJECTION 0
+
+// Log debug messages about input event throttling.
+#define DEBUG_THROTTLING 0
+
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
+#include "InputDispatcher.h"
+
+#include <cutils/log.h>
+#include <ui/PowerManager.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+#define INDENT "  "
+#define INDENT2 "    "
+
+namespace android {
+
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+
+// Amount of time to allow for all pending events to be processed when an app switch
+// key is on the way.  This is used to preempt input dispatch and drop input events
+// when an application takes too long to respond and the user has pressed an app switch key.
+const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+
+static inline nsecs_t now() {
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+static inline int32_t getMotionEventActionPointerIndex(int32_t action) {
+    return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+            >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+}
+
+static bool isValidKeyAction(int32_t action) {
+    switch (action) {
+    case AKEY_EVENT_ACTION_DOWN:
+    case AKEY_EVENT_ACTION_UP:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool validateKeyEvent(int32_t action) {
+    if (! isValidKeyAction(action)) {
+        LOGE("Key event has invalid action code 0x%x", action);
+        return false;
+    }
+    return true;
+}
+
+static bool isValidMotionAction(int32_t action, size_t pointerCount) {
+    switch (action & AMOTION_EVENT_ACTION_MASK) {
+    case AMOTION_EVENT_ACTION_DOWN:
+    case AMOTION_EVENT_ACTION_UP:
+    case AMOTION_EVENT_ACTION_CANCEL:
+    case AMOTION_EVENT_ACTION_MOVE:
+    case AMOTION_EVENT_ACTION_OUTSIDE:
+        return true;
+    case AMOTION_EVENT_ACTION_POINTER_DOWN:
+    case AMOTION_EVENT_ACTION_POINTER_UP: {
+        int32_t index = getMotionEventActionPointerIndex(action);
+        return index >= 0 && size_t(index) < pointerCount;
+    }
+    default:
+        return false;
+    }
+}
+
+static bool validateMotionEvent(int32_t action, size_t pointerCount,
+        const int32_t* pointerIds) {
+    if (! isValidMotionAction(action, pointerCount)) {
+        LOGE("Motion event has invalid action code 0x%x", action);
+        return false;
+    }
+    if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
+        LOGE("Motion event has invalid pointer count %d; value must be between 1 and %d.",
+                pointerCount, MAX_POINTERS);
+        return false;
+    }
+    BitSet32 pointerIdBits;
+    for (size_t i = 0; i < pointerCount; i++) {
+        int32_t id = pointerIds[i];
+        if (id < 0 || id > MAX_POINTER_ID) {
+            LOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
+                    id, MAX_POINTER_ID);
+            return false;
+        }
+        if (pointerIdBits.hasBit(id)) {
+            LOGE("Motion event has duplicate pointer id %d", id);
+            return false;
+        }
+        pointerIdBits.markBit(id);
+    }
+    return true;
+}
+
+
+// --- InputWindow ---
+
+bool InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
+    return x >= touchableAreaLeft && x <= touchableAreaRight
+            && y >= touchableAreaTop && y <= touchableAreaBottom;
+}
+
+bool InputWindow::frameContainsPoint(int32_t x, int32_t y) const {
+    return x >= frameLeft && x <= frameRight
+            && y >= frameTop && y <= frameBottom;
+}
+
+bool InputWindow::isTrustedOverlay() const {
+    return layoutParamsType == TYPE_INPUT_METHOD
+            || layoutParamsType == TYPE_INPUT_METHOD_DIALOG
+            || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
+}
+
+bool InputWindow::supportsSplitTouch() const {
+    return layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH;
+}
+
+
+// --- InputDispatcher ---
+
+InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
+    mPolicy(policy),
+    mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX),
+    mDispatchEnabled(true), mDispatchFrozen(false),
+    mFocusedWindow(NULL),
+    mFocusedApplication(NULL),
+    mCurrentInputTargetsValid(false),
+    mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+    mLooper = new Looper(false);
+
+    mInboundQueue.headSentinel.refCount = -1;
+    mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN;
+
+    mInboundQueue.tailSentinel.refCount = -1;
+    mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX;
+
+    mKeyRepeatState.lastKeyEntry = NULL;
+
+    int32_t maxEventsPerSecond = policy->getMaxEventsPerSecond();
+    mThrottleState.minTimeBetweenEvents = 1000000000LL / maxEventsPerSecond;
+    mThrottleState.lastDeviceId = -1;
+
+#if DEBUG_THROTTLING
+    mThrottleState.originalSampleCount = 0;
+    LOGD("Throttling - Max events per second = %d", maxEventsPerSecond);
+#endif
+}
+
+InputDispatcher::~InputDispatcher() {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        resetKeyRepeatLocked();
+        releasePendingEventLocked();
+        drainInboundQueueLocked();
+    }
+
+    while (mConnectionsByReceiveFd.size() != 0) {
+        unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
+    }
+}
+
+void InputDispatcher::dispatchOnce() {
+    nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
+    nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
+
+    nsecs_t nextWakeupTime = LONG_LONG_MAX;
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);
+
+        if (runCommandsLockedInterruptible()) {
+            nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
+        }
+    } // release lock
+
+    // Wait for callback or timeout or wake.  (make sure we round up, not down)
+    nsecs_t currentTime = now();
+    int32_t timeoutMillis;
+    if (nextWakeupTime > currentTime) {
+        uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
+        timeout = (timeout + 999999LL) / 1000000LL;
+        timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
+    } else {
+        timeoutMillis = 0;
+    }
+
+    mLooper->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
+        nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {
+    nsecs_t currentTime = now();
+
+    // Reset the key repeat timer whenever we disallow key events, even if the next event
+    // is not a key.  This is to ensure that we abort a key repeat if the device is just coming
+    // out of sleep.
+    if (keyRepeatTimeout < 0) {
+        resetKeyRepeatLocked();
+    }
+
+    // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+    if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+        LOGD("Dispatch frozen.  Waiting some more.");
+#endif
+        return;
+    }
+
+    // Optimize latency of app switches.
+    // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
+    // been pressed.  When it expires, we preempt dispatch and drop all other pending events.
+    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+    if (mAppSwitchDueTime < *nextWakeupTime) {
+        *nextWakeupTime = mAppSwitchDueTime;
+    }
+
+    // Ready to start a new event.
+    // If we don't already have a pending event, go grab one.
+    if (! mPendingEvent) {
+        if (mInboundQueue.isEmpty()) {
+            if (isAppSwitchDue) {
+                // The inbound queue is empty so the app switch key we were waiting
+                // for will never arrive.  Stop waiting for it.
+                resetPendingAppSwitchLocked(false);
+                isAppSwitchDue = false;
+            }
+
+            // Synthesize a key repeat if appropriate.
+            if (mKeyRepeatState.lastKeyEntry) {
+                if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+                    mPendingEvent = synthesizeKeyRepeatLocked(currentTime, keyRepeatDelay);
+                } else {
+                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+                    }
+                }
+            }
+            if (! mPendingEvent) {
+                return;
+            }
+        } else {
+            // Inbound queue has at least one entry.
+            EventEntry* entry = mInboundQueue.headSentinel.next;
+
+            // Throttle the entry if it is a move event and there are no
+            // other events behind it in the queue.  Due to movement batching, additional
+            // samples may be appended to this event by the time the throttling timeout
+            // expires.
+            // TODO Make this smarter and consider throttling per device independently.
+            if (entry->type == EventEntry::TYPE_MOTION
+                    && !isAppSwitchDue
+                    && mDispatchEnabled
+                    && (entry->policyFlags & POLICY_FLAG_PASS_TO_USER)
+                    && !entry->isInjected()) {
+                MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+                int32_t deviceId = motionEntry->deviceId;
+                uint32_t source = motionEntry->source;
+                if (! isAppSwitchDue
+                        && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event
+                        && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+                        && deviceId == mThrottleState.lastDeviceId
+                        && source == mThrottleState.lastSource) {
+                    nsecs_t nextTime = mThrottleState.lastEventTime
+                            + mThrottleState.minTimeBetweenEvents;
+                    if (currentTime < nextTime) {
+                        // Throttle it!
+#if DEBUG_THROTTLING
+                        LOGD("Throttling - Delaying motion event for "
+                                "device %d, source 0x%08x by up to %0.3fms.",
+                                deviceId, source, (nextTime - currentTime) * 0.000001);
+#endif
+                        if (nextTime < *nextWakeupTime) {
+                            *nextWakeupTime = nextTime;
+                        }
+                        if (mThrottleState.originalSampleCount == 0) {
+                            mThrottleState.originalSampleCount =
+                                    motionEntry->countSamples();
+                        }
+                        return;
+                    }
+                }
+
+#if DEBUG_THROTTLING
+                if (mThrottleState.originalSampleCount != 0) {
+                    uint32_t count = motionEntry->countSamples();
+                    LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
+                            count - mThrottleState.originalSampleCount,
+                            mThrottleState.originalSampleCount, count);
+                    mThrottleState.originalSampleCount = 0;
+                }
+#endif
+
+                mThrottleState.lastEventTime = entry->eventTime < currentTime
+                        ? entry->eventTime : currentTime;
+                mThrottleState.lastDeviceId = deviceId;
+                mThrottleState.lastSource = source;
+            }
+
+            mInboundQueue.dequeue(entry);
+            mPendingEvent = entry;
+        }
+
+        // Poke user activity for this event.
+        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+            pokeUserActivityLocked(mPendingEvent);
+        }
+    }
+
+    // Now we have an event to dispatch.
+    assert(mPendingEvent != NULL);
+    bool done = false;
+    DropReason dropReason = DROP_REASON_NOT_DROPPED;
+    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+        dropReason = DROP_REASON_POLICY;
+    } else if (!mDispatchEnabled) {
+        dropReason = DROP_REASON_DISABLED;
+    }
+    switch (mPendingEvent->type) {
+    case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+        ConfigurationChangedEntry* typedEntry =
+                static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+        done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+        dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
+        break;
+    }
+
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+        if (isAppSwitchDue) {
+            if (isAppSwitchKeyEventLocked(typedEntry)) {
+                resetPendingAppSwitchLocked(true);
+                isAppSwitchDue = false;
+            } else if (dropReason == DROP_REASON_NOT_DROPPED) {
+                dropReason = DROP_REASON_APP_SWITCH;
+            }
+        }
+        done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout,
+                &dropReason, nextWakeupTime);
+        break;
+    }
+
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+        if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
+            dropReason = DROP_REASON_APP_SWITCH;
+        }
+        done = dispatchMotionLocked(currentTime, typedEntry,
+                &dropReason, nextWakeupTime);
+        break;
+    }
+
+    default:
+        assert(false);
+        break;
+    }
+
+    if (done) {
+        if (dropReason != DROP_REASON_NOT_DROPPED) {
+            dropInboundEventLocked(mPendingEvent, dropReason);
+        }
+
+        releasePendingEventLocked();
+        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
+    }
+}
+
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+    bool needWake = mInboundQueue.isEmpty();
+    mInboundQueue.enqueueAtTail(entry);
+
+    switch (entry->type) {
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
+        if (isAppSwitchKeyEventLocked(keyEntry)) {
+            if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
+                mAppSwitchSawKeyDown = true;
+            } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+                if (mAppSwitchSawKeyDown) {
+#if DEBUG_APP_SWITCH
+                    LOGD("App switch is pending!");
+#endif
+                    mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
+                    mAppSwitchSawKeyDown = false;
+                    needWake = true;
+                }
+            }
+        }
+        break;
+    }
+    }
+
+    return needWake;
+}
+
+void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) {
+    const char* reason;
+    switch (dropReason) {
+    case DROP_REASON_POLICY:
+#if DEBUG_INBOUND_EVENT_DETAILS
+        LOGD("Dropped event because policy consumed it.");
+#endif
+        reason = "inbound event was dropped because the policy consumed it";
+        break;
+    case DROP_REASON_DISABLED:
+        LOGI("Dropped event because input dispatch is disabled.");
+        reason = "inbound event was dropped because input dispatch is disabled";
+        break;
+    case DROP_REASON_APP_SWITCH:
+        LOGI("Dropped event because of pending overdue app switch.");
+        reason = "inbound event was dropped because of pending overdue app switch";
+        break;
+    default:
+        assert(false);
+        return;
+    }
+
+    switch (entry->type) {
+    case EventEntry::TYPE_KEY:
+        synthesizeCancelationEventsForAllConnectionsLocked(
+                InputState::CANCEL_NON_POINTER_EVENTS, reason);
+        break;
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+        if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+            synthesizeCancelationEventsForAllConnectionsLocked(
+                    InputState::CANCEL_POINTER_EVENTS, reason);
+        } else {
+            synthesizeCancelationEventsForAllConnectionsLocked(
+                    InputState::CANCEL_NON_POINTER_EVENTS, reason);
+        }
+        break;
+    }
+    }
+}
+
+bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
+    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
+}
+
+bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
+    return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+            && isAppSwitchKeyCode(keyEntry->keyCode)
+            && (keyEntry->policyFlags & POLICY_FLAG_TRUSTED)
+            && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER);
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+    return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+    mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+    if (handled) {
+        LOGD("App switch has arrived.");
+    } else {
+        LOGD("App switch was abandoned.");
+    }
+#endif
+}
+
+bool InputDispatcher::runCommandsLockedInterruptible() {
+    if (mCommandQueue.isEmpty()) {
+        return false;
+    }
+
+    do {
+        CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();
+
+        Command command = commandEntry->command;
+        (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible'
+
+        commandEntry->connection.clear();
+        mAllocator.releaseCommandEntry(commandEntry);
+    } while (! mCommandQueue.isEmpty());
+    return true;
+}
+
+InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
+    CommandEntry* commandEntry = mAllocator.obtainCommandEntry(command);
+    mCommandQueue.enqueueAtTail(commandEntry);
+    return commandEntry;
+}
+
+void InputDispatcher::drainInboundQueueLocked() {
+    while (! mInboundQueue.isEmpty()) {
+        EventEntry* entry = mInboundQueue.dequeueAtHead();
+        releaseInboundEventLocked(entry);
+    }
+}
+
+void InputDispatcher::releasePendingEventLocked() {
+    if (mPendingEvent) {
+        releaseInboundEventLocked(mPendingEvent);
+        mPendingEvent = NULL;
+    }
+}
+
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("Injected inbound event was dropped.");
+#endif
+        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+    }
+    mAllocator.releaseEventEntry(entry);
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+    if (mKeyRepeatState.lastKeyEntry) {
+        mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
+        mKeyRepeatState.lastKeyEntry = NULL;
+    }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(
+        nsecs_t currentTime, nsecs_t keyRepeatDelay) {
+    KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
+
+    // Reuse the repeated key entry if it is otherwise unreferenced.
+    uint32_t policyFlags = (entry->policyFlags & POLICY_FLAG_RAW_MASK)
+            | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED;
+    if (entry->refCount == 1) {
+        mAllocator.recycleKeyEntry(entry);
+        entry->eventTime = currentTime;
+        entry->policyFlags = policyFlags;
+        entry->repeatCount += 1;
+    } else {
+        KeyEntry* newEntry = mAllocator.obtainKeyEntry(currentTime,
+                entry->deviceId, entry->source, policyFlags,
+                entry->action, entry->flags, entry->keyCode, entry->scanCode,
+                entry->metaState, entry->repeatCount + 1, entry->downTime);
+
+        mKeyRepeatState.lastKeyEntry = newEntry;
+        mAllocator.releaseKeyEntry(entry);
+
+        entry = newEntry;
+    }
+    entry->syntheticRepeat = true;
+
+    // Increment reference count since we keep a reference to the event in
+    // mKeyRepeatState.lastKeyEntry in addition to the one we return.
+    entry->refCount += 1;
+
+    if (entry->repeatCount == 1) {
+        entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
+    }
+
+    mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay;
+    return entry;
+}
+
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+        nsecs_t currentTime, ConfigurationChangedEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime);
+#endif
+
+    // Reset key repeating in case a keyboard device was added or removed or something.
+    resetKeyRepeatLocked();
+
+    // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doNotifyConfigurationChangedInterruptible);
+    commandEntry->eventTime = entry->eventTime;
+    return true;
+}
+
+bool InputDispatcher::dispatchKeyLocked(
+        nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+        DropReason* dropReason, nsecs_t* nextWakeupTime) {
+    // Preprocessing.
+    if (! entry->dispatchInProgress) {
+        if (entry->repeatCount == 0
+                && entry->action == AKEY_EVENT_ACTION_DOWN
+                && (entry->policyFlags & POLICY_FLAG_TRUSTED)
+                && !entry->isInjected()) {
+            if (mKeyRepeatState.lastKeyEntry
+                    && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+                // We have seen two identical key downs in a row which indicates that the device
+                // driver is automatically generating key repeats itself.  We take note of the
+                // repeat here, but we disable our own next key repeat timer since it is clear that
+                // we will not need to synthesize key repeats ourselves.
+                entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+                resetKeyRepeatLocked();
+                mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+            } else {
+                // Not a repeat.  Save key down state in case we do see a repeat later.
+                resetKeyRepeatLocked();
+                mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
+            }
+            mKeyRepeatState.lastKeyEntry = entry;
+            entry->refCount += 1;
+        } else if (! entry->syntheticRepeat) {
+            resetKeyRepeatLocked();
+        }
+
+        entry->dispatchInProgress = true;
+        resetTargetsLocked();
+
+        logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+    }
+
+    // Give the policy a chance to intercept the key.
+    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+            CommandEntry* commandEntry = postCommandLocked(
+                    & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+            if (mFocusedWindow) {
+                commandEntry->inputChannel = mFocusedWindow->inputChannel;
+            }
+            commandEntry->keyEntry = entry;
+            entry->refCount += 1;
+            return false; // wait for the command to run
+        } else {
+            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+        }
+    } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+        if (*dropReason == DROP_REASON_NOT_DROPPED) {
+            *dropReason = DROP_REASON_POLICY;
+        }
+    }
+
+    // Clean up if dropping the event.
+    if (*dropReason != DROP_REASON_NOT_DROPPED) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+                ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+        return true;
+    }
+
+    // Identify targets.
+    if (! mCurrentInputTargetsValid) {
+        int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
+                entry, nextWakeupTime);
+        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+            return false;
+        }
+
+        setInjectionResultLocked(entry, injectionResult);
+        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+            return true;
+        }
+
+        addMonitoringTargetsLocked();
+        commitTargetsLocked();
+    }
+
+    // Dispatch the key.
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+    return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+            "repeatCount=%d, downTime=%lld",
+            prefix,
+            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+            entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+            entry->repeatCount, entry->downTime);
+#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
+    // Preprocessing.
+    if (! entry->dispatchInProgress) {
+        entry->dispatchInProgress = true;
+        resetTargetsLocked();
+
+        logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+    }
+
+    // Clean up if dropping the event.
+    if (*dropReason != DROP_REASON_NOT_DROPPED) {
+        resetTargetsLocked();
+        setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+                ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+        return true;
+    }
+
+    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+    // Identify targets.
+    if (! mCurrentInputTargetsValid) {
+        int32_t injectionResult;
+        if (isPointerEvent) {
+            // Pointer event.  (eg. touchscreen)
+            injectionResult = findTouchedWindowTargetsLocked(currentTime,
+                    entry, nextWakeupTime);
+        } else {
+            // Non touch event.  (eg. trackball)
+            injectionResult = findFocusedWindowTargetsLocked(currentTime,
+                    entry, nextWakeupTime);
+        }
+        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+            return false;
+        }
+
+        setInjectionResultLocked(entry, injectionResult);
+        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+            return true;
+        }
+
+        addMonitoringTargetsLocked();
+        commitTargetsLocked();
+    }
+
+    // Dispatch the motion.
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+    return true;
+}
+
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, "
+            "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
+            prefix,
+            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+            entry->action, entry->flags,
+            entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
+            entry->downTime);
+
+    // Print the most recent sample that we have available, this may change due to batching.
+    size_t sampleCount = 1;
+    const MotionSample* sample = & entry->firstSample;
+    for (; sample->next != NULL; sample = sample->next) {
+        sampleCount += 1;
+    }
+    for (uint32_t i = 0; i < entry->pointerCount; i++) {
+        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
+                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+                "orientation=%f",
+                i, entry->pointerIds[i],
+                sample->pointerCoords[i].x, sample->pointerCoords[i].y,
+                sample->pointerCoords[i].pressure, sample->pointerCoords[i].size,
+                sample->pointerCoords[i].touchMajor, sample->pointerCoords[i].touchMinor,
+                sample->pointerCoords[i].toolMajor, sample->pointerCoords[i].toolMinor,
+                sample->pointerCoords[i].orientation);
+    }
+
+    // Keep in mind that due to batching, it is possible for the number of samples actually
+    // dispatched to change before the application finally consumed them.
+    if (entry->action == AMOTION_EVENT_ACTION_MOVE) {
+        LOGD("  ... Total movement samples currently batched %d ...", sampleCount);
+    }
+#endif
+}
+
+void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
+        EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("dispatchEventToCurrentInputTargets - "
+            "resumeWithAppendedMotionSample=%s",
+            toString(resumeWithAppendedMotionSample));
+#endif
+
+    assert(eventEntry->dispatchInProgress); // should already have been set to true
+
+    pokeUserActivityLocked(eventEntry);
+
+    for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+        const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
+
+        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+        if (connectionIndex >= 0) {
+            sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+            prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
+                    resumeWithAppendedMotionSample);
+        } else {
+#if DEBUG_FOCUS
+            LOGD("Dropping event delivery to target with channel '%s' because it "
+                    "is no longer registered with the input dispatcher.",
+                    inputTarget.inputChannel->getName().string());
+#endif
+        }
+    }
+}
+
+void InputDispatcher::resetTargetsLocked() {
+    mCurrentInputTargetsValid = false;
+    mCurrentInputTargets.clear();
+    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+
+void InputDispatcher::commitTargetsLocked() {
+    mCurrentInputTargetsValid = true;
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+        const EventEntry* entry, const InputApplication* application, const InputWindow* window,
+        nsecs_t* nextWakeupTime) {
+    if (application == NULL && window == NULL) {
+        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+            LOGD("Waiting for system to become ready for input.");
+#endif
+            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+            mInputTargetWaitStartTime = currentTime;
+            mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+            mInputTargetWaitTimeoutExpired = false;
+        }
+    } else {
+        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+            LOGD("Waiting for application to become ready for input: %s",
+                    getApplicationWindowLabelLocked(application, window).string());
+#endif
+            nsecs_t timeout = window ? window->dispatchingTimeout :
+                application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+
+            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+            mInputTargetWaitStartTime = currentTime;
+            mInputTargetWaitTimeoutTime = currentTime + timeout;
+            mInputTargetWaitTimeoutExpired = false;
+        }
+    }
+
+    if (mInputTargetWaitTimeoutExpired) {
+        return INPUT_EVENT_INJECTION_TIMED_OUT;
+    }
+
+    if (currentTime >= mInputTargetWaitTimeoutTime) {
+        onANRLocked(currentTime, application, window, entry->eventTime, mInputTargetWaitStartTime);
+
+        // Force poll loop to wake up immediately on next iteration once we get the
+        // ANR response back from the policy.
+        *nextWakeupTime = LONG_LONG_MIN;
+        return INPUT_EVENT_INJECTION_PENDING;
+    } else {
+        // Force poll loop to wake up when timeout is due.
+        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
+            *nextWakeupTime = mInputTargetWaitTimeoutTime;
+        }
+        return INPUT_EVENT_INJECTION_PENDING;
+    }
+}
+
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+        const sp<InputChannel>& inputChannel) {
+    if (newTimeout > 0) {
+        // Extend the timeout.
+        mInputTargetWaitTimeoutTime = now() + newTimeout;
+    } else {
+        // Give up.
+        mInputTargetWaitTimeoutExpired = true;
+
+        // Release the touch targets.
+        mTouchState.reset();
+
+        // Input state will not be realistic.  Mark it out of sync.
+        if (inputChannel.get()) {
+            ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+            if (connectionIndex >= 0) {
+                sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+                if (connection->status == Connection::STATUS_NORMAL) {
+                    synthesizeCancelationEventsForConnectionLocked(
+                            connection, InputState::CANCEL_ALL_EVENTS,
+                            "application not responding");
+                }
+            }
+        }
+    }
+}
+
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
+        nsecs_t currentTime) {
+    if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+        return currentTime - mInputTargetWaitStartTime;
+    }
+    return 0;
+}
+
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+        LOGD("Resetting ANR timeouts.");
+#endif
+
+    // Reset input target wait timeout.
+    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+
+int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
+        const EventEntry* entry, nsecs_t* nextWakeupTime) {
+    mCurrentInputTargets.clear();
+
+    int32_t injectionResult;
+
+    // If there is no currently focused window and no focused application
+    // then drop the event.
+    if (! mFocusedWindow) {
+        if (mFocusedApplication) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because there is no focused window but there is a "
+                    "focused application that may eventually add a window: %s.",
+                    getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    mFocusedApplication, NULL, nextWakeupTime);
+            goto Unresponsive;
+        }
+
+        LOGI("Dropping event because there is no focused window or focused application.");
+        injectionResult = INPUT_EVENT_INJECTION_FAILED;
+        goto Failed;
+    }
+
+    // Check permissions.
+    if (! checkInjectionPermission(mFocusedWindow, entry->injectionState)) {
+        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+        goto Failed;
+    }
+
+    // If the currently focused window is paused then keep waiting.
+    if (mFocusedWindow->paused) {
+#if DEBUG_FOCUS
+        LOGD("Waiting because focused window is paused.");
+#endif
+        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                mFocusedApplication, mFocusedWindow, nextWakeupTime);
+        goto Unresponsive;
+    }
+
+    // If the currently focused window is still working on previous events then keep waiting.
+    if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindow)) {
+#if DEBUG_FOCUS
+        LOGD("Waiting because focused window still processing previous input.");
+#endif
+        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                mFocusedApplication, mFocusedWindow, nextWakeupTime);
+        goto Unresponsive;
+    }
+
+    // Success!  Output targets.
+    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+    addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0));
+
+    // Done.
+Failed:
+Unresponsive:
+    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+    updateDispatchStatisticsLocked(currentTime, entry,
+            injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+    LOGD("findFocusedWindow finished: injectionResult=%d, "
+            "timeSpendWaitingForApplication=%0.1fms",
+            injectionResult, timeSpentWaitingForApplication / 1000000.0);
+#endif
+    return injectionResult;
+}
+
+int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
+        const MotionEntry* entry, nsecs_t* nextWakeupTime) {
+    enum InjectionPermission {
+        INJECTION_PERMISSION_UNKNOWN,
+        INJECTION_PERMISSION_GRANTED,
+        INJECTION_PERMISSION_DENIED
+    };
+
+    mCurrentInputTargets.clear();
+
+    nsecs_t startTime = now();
+
+    // For security reasons, we defer updating the touch state until we are sure that
+    // event injection will be allowed.
+    //
+    // FIXME In the original code, screenWasOff could never be set to true.
+    //       The reason is that the POLICY_FLAG_WOKE_HERE
+    //       and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
+    //       EV_KEY, EV_REL and EV_ABS events.  As it happens, the touch event was
+    //       actually enqueued using the policyFlags that appeared in the final EV_SYN
+    //       events upon which no preprocessing took place.  So policyFlags was always 0.
+    //       In the new native input dispatcher we're a bit more careful about event
+    //       preprocessing so the touches we receive can actually have non-zero policyFlags.
+    //       Unfortunately we obtain undesirable behavior.
+    //
+    //       Here's what happens:
+    //
+    //       When the device dims in anticipation of going to sleep, touches
+    //       in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
+    //       the device to brighten and reset the user activity timer.
+    //       Touches on other windows (such as the launcher window)
+    //       are dropped.  Then after a moment, the device goes to sleep.  Oops.
+    //
+    //       Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
+    //       instead of POLICY_FLAG_WOKE_HERE...
+    //
+    bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
+
+    int32_t action = entry->action;
+    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+
+    // Update the touch state as needed based on the properties of the touch event.
+    int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
+    InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+        mTempTouchState.reset();
+        mTempTouchState.down = true;
+    } else {
+        mTempTouchState.copyFrom(mTouchState);
+    }
+
+    bool isSplit = mTempTouchState.split && mTempTouchState.down;
+    if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+            || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+        /* Case 1: New splittable pointer going down. */
+
+        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+        int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex].x);
+        int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex].y);
+        const InputWindow* newTouchedWindow = NULL;
+        const InputWindow* topErrorWindow = NULL;
+
+        // Traverse windows from front to back to find touched window and outside targets.
+        size_t numWindows = mWindows.size();
+        for (size_t i = 0; i < numWindows; i++) {
+            const InputWindow* window = & mWindows.editItemAt(i);
+            int32_t flags = window->layoutParamsFlags;
+
+            if (flags & InputWindow::FLAG_SYSTEM_ERROR) {
+                if (! topErrorWindow) {
+                    topErrorWindow = window;
+                }
+            }
+
+            if (window->visible) {
+                if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
+                    bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
+                            | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
+                    if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
+                        if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) {
+                            newTouchedWindow = window;
+                        }
+                        break; // found touched window, exit window loop
+                    }
+                }
+
+                if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+                        && (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH)) {
+                    int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
+                    if (isWindowObscuredAtPointLocked(window, x, y)) {
+                        outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+                    }
+
+                    mTempTouchState.addOrUpdateWindow(window, outsideTargetFlags, BitSet32(0));
+                }
+            }
+        }
+
+        // If there is an error window but it is not taking focus (typically because
+        // it is invisible) then wait for it.  Any other focused window may in
+        // fact be in ANR state.
+        if (topErrorWindow && newTouchedWindow != topErrorWindow) {
+#if DEBUG_FOCUS
+            LOGD("Waiting because system error window is pending.");
+#endif
+            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                    NULL, NULL, nextWakeupTime);
+            injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+            goto Unresponsive;
+        }
+
+        // Figure out whether splitting will be allowed for this window.
+        if (newTouchedWindow && newTouchedWindow->supportsSplitTouch()) {
+            // New window supports splitting.
+            isSplit = true;
+        } else if (isSplit) {
+            // New window does not support splitting but we have already split events.
+            // Assign the pointer to the first foreground window we find.
+            // (May be NULL which is why we put this code block before the next check.)
+            newTouchedWindow = mTempTouchState.getFirstForegroundWindow();
+        }
+
+        // If we did not find a touched window then fail.
+        if (! newTouchedWindow) {
+            if (mFocusedApplication) {
+#if DEBUG_FOCUS
+                LOGD("Waiting because there is no touched window but there is a "
+                        "focused application that may eventually add a new window: %s.",
+                        getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                        mFocusedApplication, NULL, nextWakeupTime);
+                goto Unresponsive;
+            }
+
+            LOGI("Dropping event because there is no touched window or focused application.");
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            goto Failed;
+        }
+
+        // Set target flags.
+        int32_t targetFlags = InputTarget::FLAG_FOREGROUND;
+        if (isSplit) {
+            targetFlags |= InputTarget::FLAG_SPLIT;
+        }
+        if (isWindowObscuredAtPointLocked(newTouchedWindow, x, y)) {
+            targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+        }
+
+        // Update the temporary touch state.
+        BitSet32 pointerIds;
+        if (isSplit) {
+            uint32_t pointerId = entry->pointerIds[pointerIndex];
+            pointerIds.markBit(pointerId);
+        }
+        mTempTouchState.addOrUpdateWindow(newTouchedWindow, targetFlags, pointerIds);
+    } else {
+        /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
+
+        // If the pointer is not currently down, then ignore the event.
+        if (! mTempTouchState.down) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+            LOGD("Dropping event because the pointer is not down or we previously "
+                    "dropped the pointer down event.");
+#endif
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            goto Failed;
+        }
+    }
+
+    // Check permission to inject into all touched foreground windows and ensure there
+    // is at least one touched foreground window.
+    {
+        bool haveForegroundWindow = false;
+        for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+            if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+                haveForegroundWindow = true;
+                if (! checkInjectionPermission(touchedWindow.window, entry->injectionState)) {
+                    injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+                    injectionPermission = INJECTION_PERMISSION_DENIED;
+                    goto Failed;
+                }
+            }
+        }
+        if (! haveForegroundWindow) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+            LOGD("Dropping event because there is no touched foreground window to receive it.");
+#endif
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            goto Failed;
+        }
+
+        // Permission granted to injection into all touched foreground windows.
+        injectionPermission = INJECTION_PERMISSION_GRANTED;
+    }
+
+    // Ensure all touched foreground windows are ready for new input.
+    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+        const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+        if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+            // If the touched window is paused then keep waiting.
+            if (touchedWindow.window->paused) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+                LOGD("Waiting because touched window is paused.");
+#endif
+                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                        NULL, touchedWindow.window, nextWakeupTime);
+                goto Unresponsive;
+            }
+
+            // If the touched window is still working on previous events then keep waiting.
+            if (! isWindowFinishedWithPreviousInputLocked(touchedWindow.window)) {
+#if DEBUG_FOCUS
+                LOGD("Waiting because touched window still processing previous input.");
+#endif
+                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+                        NULL, touchedWindow.window, nextWakeupTime);
+                goto Unresponsive;
+            }
+        }
+    }
+
+    // If this is the first pointer going down and the touched window has a wallpaper
+    // then also add the touched wallpaper windows so they are locked in for the duration
+    // of the touch gesture.
+    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+        const InputWindow* foregroundWindow = mTempTouchState.getFirstForegroundWindow();
+        if (foregroundWindow->hasWallpaper) {
+            for (size_t i = 0; i < mWindows.size(); i++) {
+                const InputWindow* window = & mWindows[i];
+                if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) {
+                    mTempTouchState.addOrUpdateWindow(window,
+                            InputTarget::FLAG_WINDOW_IS_OBSCURED, BitSet32(0));
+                }
+            }
+        }
+    }
+
+    // Success!  Output targets.
+    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+
+    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+        const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
+        addWindowTargetLocked(touchedWindow.window, touchedWindow.targetFlags,
+                touchedWindow.pointerIds);
+    }
+
+    // Drop the outside touch window since we will not care about them in the next iteration.
+    mTempTouchState.removeOutsideTouchWindows();
+
+Failed:
+    // Check injection permission once and for all.
+    if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+        if (checkInjectionPermission(NULL, entry->injectionState)) {
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+        } else {
+            injectionPermission = INJECTION_PERMISSION_DENIED;
+        }
+    }
+
+    // Update final pieces of touch state if the injector had permission.
+    if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+        if (maskedAction == AMOTION_EVENT_ACTION_UP
+                || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+            // All pointers up or canceled.
+            mTempTouchState.reset();
+        } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+            // First pointer went down.
+            if (mTouchState.down) {
+#if DEBUG_FOCUS
+                LOGD("Pointer down received while already down.");
+#endif
+            }
+        } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+            // One pointer went up.
+            if (isSplit) {
+                int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+                uint32_t pointerId = entry->pointerIds[pointerIndex];
+
+                for (size_t i = 0; i < mTempTouchState.windows.size(); ) {
+                    TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i);
+                    if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) {
+                        touchedWindow.pointerIds.clearBit(pointerId);
+                        if (touchedWindow.pointerIds.isEmpty()) {
+                            mTempTouchState.windows.removeAt(i);
+                            continue;
+                        }
+                    }
+                    i += 1;
+                }
+            }
+        }
+
+        // Save changes to touch state.
+        mTouchState.copyFrom(mTempTouchState);
+    } else {
+#if DEBUG_FOCUS
+        LOGD("Not updating touch focus because injection was denied.");
+#endif
+    }
+
+Unresponsive:
+    // Reset temporary touch state to ensure we release unnecessary references to input channels.
+    mTempTouchState.reset();
+
+    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+    updateDispatchStatisticsLocked(currentTime, entry,
+            injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+    LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, "
+            "timeSpentWaitingForApplication=%0.1fms",
+            injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
+#endif
+    return injectionResult;
+}
+
+void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
+        BitSet32 pointerIds) {
+    mCurrentInputTargets.push();
+
+    InputTarget& target = mCurrentInputTargets.editTop();
+    target.inputChannel = window->inputChannel;
+    target.flags = targetFlags;
+    target.xOffset = - window->frameLeft;
+    target.yOffset = - window->frameTop;
+    target.pointerIds = pointerIds;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked() {
+    for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+        mCurrentInputTargets.push();
+
+        InputTarget& target = mCurrentInputTargets.editTop();
+        target.inputChannel = mMonitoringChannels[i];
+        target.flags = 0;
+        target.xOffset = 0;
+        target.yOffset = 0;
+    }
+}
+
+bool InputDispatcher::checkInjectionPermission(const InputWindow* window,
+        const InjectionState* injectionState) {
+    if (injectionState
+            && (window == NULL || window->ownerUid != injectionState->injectorUid)
+            && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
+        if (window) {
+            LOGW("Permission denied: injecting event from pid %d uid %d to window "
+                    "with input channel %s owned by uid %d",
+                    injectionState->injectorPid, injectionState->injectorUid,
+                    window->inputChannel->getName().string(),
+                    window->ownerUid);
+        } else {
+            LOGW("Permission denied: injecting event from pid %d uid %d",
+                    injectionState->injectorPid, injectionState->injectorUid);
+        }
+        return false;
+    }
+    return true;
+}
+
+bool InputDispatcher::isWindowObscuredAtPointLocked(
+        const InputWindow* window, int32_t x, int32_t y) const {
+    size_t numWindows = mWindows.size();
+    for (size_t i = 0; i < numWindows; i++) {
+        const InputWindow* other = & mWindows.itemAt(i);
+        if (other == window) {
+            break;
+        }
+        if (other->visible && ! other->isTrustedOverlay() && other->frameContainsPoint(x, y)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool InputDispatcher::isWindowFinishedWithPreviousInputLocked(const InputWindow* window) {
+    ssize_t connectionIndex = getConnectionIndexLocked(window->inputChannel);
+    if (connectionIndex >= 0) {
+        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        return connection->outboundQueue.isEmpty();
+    } else {
+        return true;
+    }
+}
+
+String8 InputDispatcher::getApplicationWindowLabelLocked(const InputApplication* application,
+        const InputWindow* window) {
+    if (application) {
+        if (window) {
+            String8 label(application->name);
+            label.append(" - ");
+            label.append(window->name);
+            return label;
+        } else {
+            return application->name;
+        }
+    } else if (window) {
+        return window->name;
+    } else {
+        return String8("<unknown application or window>");
+    }
+}
+
+void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {
+    int32_t eventType = POWER_MANAGER_BUTTON_EVENT;
+    switch (eventEntry->type) {
+    case EventEntry::TYPE_MOTION: {
+        const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry);
+        if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) {
+            return;
+        }
+
+        if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+            eventType = POWER_MANAGER_TOUCH_EVENT;
+        }
+        break;
+    }
+    case EventEntry::TYPE_KEY: {
+        const KeyEntry* keyEntry = static_cast<const KeyEntry*>(eventEntry);
+        if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) {
+            return;
+        }
+        break;
+    }
+    }
+
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doPokeUserActivityLockedInterruptible);
+    commandEntry->eventTime = eventEntry->eventTime;
+    commandEntry->userActivityEventType = eventType;
+}
+
+void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
+        bool resumeWithAppendedMotionSample) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, "
+            "xOffset=%f, yOffset=%f, "
+            "pointerIds=0x%x, "
+            "resumeWithAppendedMotionSample=%s",
+            connection->getInputChannelName(), inputTarget->flags,
+            inputTarget->xOffset, inputTarget->yOffset,
+            inputTarget->pointerIds.value,
+            toString(resumeWithAppendedMotionSample));
+#endif
+
+    // Make sure we are never called for streaming when splitting across multiple windows.
+    bool isSplit = inputTarget->flags & InputTarget::FLAG_SPLIT;
+    assert(! (resumeWithAppendedMotionSample && isSplit));
+
+    // Skip this event if the connection status is not normal.
+    // We don't want to enqueue additional outbound events if the connection is broken.
+    if (connection->status != Connection::STATUS_NORMAL) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Dropping event because the channel status is %s",
+                connection->getInputChannelName(), connection->getStatusLabel());
+#endif
+        return;
+    }
+
+    // Split a motion event if needed.
+    if (isSplit) {
+        assert(eventEntry->type == EventEntry::TYPE_MOTION);
+
+        MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
+        if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
+            MotionEntry* splitMotionEntry = splitMotionEvent(
+                    originalMotionEntry, inputTarget->pointerIds);
+#if DEBUG_FOCUS
+            LOGD("channel '%s' ~ Split motion event.",
+                    connection->getInputChannelName());
+            logOutboundMotionDetailsLocked("  ", splitMotionEntry);
+#endif
+            eventEntry = splitMotionEntry;
+        }
+    }
+
+    // Resume the dispatch cycle with a freshly appended motion sample.
+    // First we check that the last dispatch entry in the outbound queue is for the same
+    // motion event to which we appended the motion sample.  If we find such a dispatch
+    // entry, and if it is currently in progress then we try to stream the new sample.
+    bool wasEmpty = connection->outboundQueue.isEmpty();
+
+    if (! wasEmpty && resumeWithAppendedMotionSample) {
+        DispatchEntry* motionEventDispatchEntry =
+                connection->findQueuedDispatchEntryForEvent(eventEntry);
+        if (motionEventDispatchEntry) {
+            // If the dispatch entry is not in progress, then we must be busy dispatching an
+            // earlier event.  Not a problem, the motion event is on the outbound queue and will
+            // be dispatched later.
+            if (! motionEventDispatchEntry->inProgress) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Not streaming because the motion event has "
+                        "not yet been dispatched.  "
+                        "(Waiting for earlier events to be consumed.)",
+                        connection->getInputChannelName());
+#endif
+                return;
+            }
+
+            // If the dispatch entry is in progress but it already has a tail of pending
+            // motion samples, then it must mean that the shared memory buffer filled up.
+            // Not a problem, when this dispatch cycle is finished, we will eventually start
+            // a new dispatch cycle to process the tail and that tail includes the newly
+            // appended motion sample.
+            if (motionEventDispatchEntry->tailMotionSample) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Not streaming because no new samples can "
+                        "be appended to the motion event in this dispatch cycle.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName());
+#endif
+                return;
+            }
+
+            // The dispatch entry is in progress and is still potentially open for streaming.
+            // Try to stream the new motion sample.  This might fail if the consumer has already
+            // consumed the motion event (or if the channel is broken).
+            MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+            MotionSample* appendedMotionSample = motionEntry->lastSample;
+            status_t status = connection->inputPublisher.appendMotionSample(
+                    appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
+            if (status == OK) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Successfully streamed new motion sample.",
+                        connection->getInputChannelName());
+#endif
+                return;
+            }
+
+#if DEBUG_BATCHING
+            if (status == NO_MEMORY) {
+                LOGD("channel '%s' ~ Could not append motion sample to currently "
+                        "dispatched move event because the shared memory buffer is full.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName());
+            } else if (status == status_t(FAILED_TRANSACTION)) {
+                LOGD("channel '%s' ~ Could not append motion sample to currently "
+                        "dispatched move event because the event has already been consumed.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName());
+            } else {
+                LOGD("channel '%s' ~ Could not append motion sample to currently "
+                        "dispatched move event due to an error, status=%d.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName(), status);
+            }
+#endif
+            // Failed to stream.  Start a new tail of pending motion samples to dispatch
+            // in the next cycle.
+            motionEventDispatchEntry->tailMotionSample = appendedMotionSample;
+            return;
+        }
+    }
+
+    // This is a new event.
+    // Enqueue a new dispatch entry onto the outbound queue for this connection.
+    DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
+            inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset);
+    if (dispatchEntry->hasForegroundTarget()) {
+        incrementPendingForegroundDispatchesLocked(eventEntry);
+    }
+
+    // Handle the case where we could not stream a new motion sample because the consumer has
+    // already consumed the motion event (otherwise the corresponding dispatch entry would
+    // still be in the outbound queue for this connection).  We set the head motion sample
+    // to the list starting with the newly appended motion sample.
+    if (resumeWithAppendedMotionSample) {
+#if DEBUG_BATCHING
+        LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples "
+                "that cannot be streamed because the motion event has already been consumed.",
+                connection->getInputChannelName());
+#endif
+        MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
+        dispatchEntry->headMotionSample = appendedMotionSample;
+    }
+
+    // Enqueue the dispatch entry.
+    connection->outboundQueue.enqueueAtTail(dispatchEntry);
+
+    // If the outbound queue was previously empty, start the dispatch cycle going.
+    if (wasEmpty) {
+        activateConnectionLocked(connection.get());
+        startDispatchCycleLocked(currentTime, connection);
+    }
+}
+
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ startDispatchCycle",
+            connection->getInputChannelName());
+#endif
+
+    assert(connection->status == Connection::STATUS_NORMAL);
+    assert(! connection->outboundQueue.isEmpty());
+
+    DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+    assert(! dispatchEntry->inProgress);
+
+    // Mark the dispatch entry as in progress.
+    dispatchEntry->inProgress = true;
+
+    // Update the connection's input state.
+    EventEntry* eventEntry = dispatchEntry->eventEntry;
+    InputState::Consistency consistency = connection->inputState.trackEvent(eventEntry);
+
+#if FILTER_INPUT_EVENTS
+    // Filter out inconsistent sequences of input events.
+    // The input system may drop or inject events in a way that could violate implicit
+    // invariants on input state and potentially cause an application to crash
+    // or think that a key or pointer is stuck down.  Technically we make no guarantees
+    // of consistency but it would be nice to improve on this where possible.
+    // XXX: This code is a proof of concept only.  Not ready for prime time.
+    if (consistency == InputState::TOLERABLE) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's "
+                "current input state but that is likely to be tolerated by the application.",
+                connection->getInputChannelName());
+#endif
+    } else if (consistency == InputState::BROKEN) {
+        LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's "
+                "current input state and that is likely to cause the application to crash.",
+                connection->getInputChannelName());
+        startNextDispatchCycleLocked(currentTime, connection);
+        return;
+    }
+#endif
+
+    // Publish the event.
+    status_t status;
+    switch (eventEntry->type) {
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+
+        // Apply target flags.
+        int32_t action = keyEntry->action;
+        int32_t flags = keyEntry->flags;
+
+        // Publish the key event.
+        status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
+                action, flags, keyEntry->keyCode, keyEntry->scanCode,
+                keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
+                keyEntry->eventTime);
+
+        if (status) {
+            LOGE("channel '%s' ~ Could not publish key event, "
+                    "status=%d", connection->getInputChannelName(), status);
+            abortBrokenDispatchCycleLocked(currentTime, connection);
+            return;
+        }
+        break;
+    }
+
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+
+        // Apply target flags.
+        int32_t action = motionEntry->action;
+        int32_t flags = motionEntry->flags;
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
+            action = AMOTION_EVENT_ACTION_OUTSIDE;
+        }
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+            flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        }
+
+        // If headMotionSample is non-NULL, then it points to the first new sample that we
+        // were unable to dispatch during the previous cycle so we resume dispatching from
+        // that point in the list of motion samples.
+        // Otherwise, we just start from the first sample of the motion event.
+        MotionSample* firstMotionSample = dispatchEntry->headMotionSample;
+        if (! firstMotionSample) {
+            firstMotionSample = & motionEntry->firstSample;
+        }
+
+        // Set the X and Y offset depending on the input source.
+        float xOffset, yOffset;
+        if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+            xOffset = dispatchEntry->xOffset;
+            yOffset = dispatchEntry->yOffset;
+        } else {
+            xOffset = 0.0f;
+            yOffset = 0.0f;
+        }
+
+        // Publish the motion event and the first motion sample.
+        status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
+                motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
+                xOffset, yOffset,
+                motionEntry->xPrecision, motionEntry->yPrecision,
+                motionEntry->downTime, firstMotionSample->eventTime,
+                motionEntry->pointerCount, motionEntry->pointerIds,
+                firstMotionSample->pointerCoords);
+
+        if (status) {
+            LOGE("channel '%s' ~ Could not publish motion event, "
+                    "status=%d", connection->getInputChannelName(), status);
+            abortBrokenDispatchCycleLocked(currentTime, connection);
+            return;
+        }
+
+        // Append additional motion samples.
+        MotionSample* nextMotionSample = firstMotionSample->next;
+        for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
+            status = connection->inputPublisher.appendMotionSample(
+                    nextMotionSample->eventTime, nextMotionSample->pointerCoords);
+            if (status == NO_MEMORY) {
+#if DEBUG_DISPATCH_CYCLE
+                    LOGD("channel '%s' ~ Shared memory buffer full.  Some motion samples will "
+                            "be sent in the next dispatch cycle.",
+                            connection->getInputChannelName());
+#endif
+                break;
+            }
+            if (status != OK) {
+                LOGE("channel '%s' ~ Could not append motion sample "
+                        "for a reason other than out of memory, status=%d",
+                        connection->getInputChannelName(), status);
+                abortBrokenDispatchCycleLocked(currentTime, connection);
+                return;
+            }
+        }
+
+        // Remember the next motion sample that we could not dispatch, in case we ran out
+        // of space in the shared memory buffer.
+        dispatchEntry->tailMotionSample = nextMotionSample;
+        break;
+    }
+
+    default: {
+        assert(false);
+    }
+    }
+
+    // Send the dispatch signal.
+    status = connection->inputPublisher.sendDispatchSignal();
+    if (status) {
+        LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
+                connection->getInputChannelName(), status);
+        abortBrokenDispatchCycleLocked(currentTime, connection);
+        return;
+    }
+
+    // Record information about the newly started dispatch cycle.
+    connection->lastEventTime = eventEntry->eventTime;
+    connection->lastDispatchTime = currentTime;
+
+    // Notify other system components.
+    onDispatchCycleStartedLocked(currentTime, connection);
+}
+
+void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection, bool handled) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, "
+            "%01.1fms since dispatch, handled=%s",
+            connection->getInputChannelName(),
+            connection->getEventLatencyMillis(currentTime),
+            connection->getDispatchLatencyMillis(currentTime),
+            toString(handled));
+#endif
+
+    if (connection->status == Connection::STATUS_BROKEN
+            || connection->status == Connection::STATUS_ZOMBIE) {
+        return;
+    }
+
+    // Reset the publisher since the event has been consumed.
+    // We do this now so that the publisher can release some of its internal resources
+    // while waiting for the next dispatch cycle to begin.
+    status_t status = connection->inputPublisher.reset();
+    if (status) {
+        LOGE("channel '%s' ~ Could not reset publisher, status=%d",
+                connection->getInputChannelName(), status);
+        abortBrokenDispatchCycleLocked(currentTime, connection);
+        return;
+    }
+
+    // Notify other system components and prepare to start the next dispatch cycle.
+    onDispatchCycleFinishedLocked(currentTime, connection, handled);
+}
+
+void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
+    // Start the next dispatch cycle for this connection.
+    while (! connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+        if (dispatchEntry->inProgress) {
+             // Finish or resume current event in progress.
+            if (dispatchEntry->tailMotionSample) {
+                // We have a tail of undispatched motion samples.
+                // Reuse the same DispatchEntry and start a new cycle.
+                dispatchEntry->inProgress = false;
+                dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample;
+                dispatchEntry->tailMotionSample = NULL;
+                startDispatchCycleLocked(currentTime, connection);
+                return;
+            }
+            // Finished.
+            connection->outboundQueue.dequeueAtHead();
+            if (dispatchEntry->hasForegroundTarget()) {
+                decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+            }
+            mAllocator.releaseDispatchEntry(dispatchEntry);
+        } else {
+            // If the head is not in progress, then we must have already dequeued the in
+            // progress event, which means we actually aborted it.
+            // So just start the next event for this connection.
+            startDispatchCycleLocked(currentTime, connection);
+            return;
+        }
+    }
+
+    // Outbound queue is empty, deactivate the connection.
+    deactivateConnectionLocked(connection.get());
+}
+
+void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ abortBrokenDispatchCycle",
+            connection->getInputChannelName());
+#endif
+
+    // Clear the outbound queue.
+    drainOutboundQueueLocked(connection.get());
+
+    // The connection appears to be unrecoverably broken.
+    // Ignore already broken or zombie connections.
+    if (connection->status == Connection::STATUS_NORMAL) {
+        connection->status = Connection::STATUS_BROKEN;
+
+        // Notify other system components.
+        onDispatchCycleBrokenLocked(currentTime, connection);
+    }
+}
+
+void InputDispatcher::drainOutboundQueueLocked(Connection* connection) {
+    while (! connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
+        if (dispatchEntry->hasForegroundTarget()) {
+            decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+        }
+        mAllocator.releaseDispatchEntry(dispatchEntry);
+    }
+
+    deactivateConnectionLocked(connection);
+}
+
+int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
+    InputDispatcher* d = static_cast<InputDispatcher*>(data);
+
+    { // acquire lock
+        AutoMutex _l(d->mLock);
+
+        ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd);
+        if (connectionIndex < 0) {
+            LOGE("Received spurious receive callback for unknown input channel.  "
+                    "fd=%d, events=0x%x", receiveFd, events);
+            return 0; // remove the callback
+        }
+
+        nsecs_t currentTime = now();
+
+        sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
+        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+            LOGE("channel '%s' ~ Consumer closed input channel or an error occurred.  "
+                    "events=0x%x", connection->getInputChannelName(), events);
+            d->abortBrokenDispatchCycleLocked(currentTime, connection);
+            d->runCommandsLockedInterruptible();
+            return 0; // remove the callback
+        }
+
+        if (! (events & ALOOPER_EVENT_INPUT)) {
+            LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
+                    "events=0x%x", connection->getInputChannelName(), events);
+            return 1;
+        }
+
+        bool handled = false;
+        status_t status = connection->inputPublisher.receiveFinishedSignal(&handled);
+        if (status) {
+            LOGE("channel '%s' ~ Failed to receive finished signal.  status=%d",
+                    connection->getInputChannelName(), status);
+            d->abortBrokenDispatchCycleLocked(currentTime, connection);
+            d->runCommandsLockedInterruptible();
+            return 0; // remove the callback
+        }
+
+        d->finishDispatchCycleLocked(currentTime, connection, handled);
+        d->runCommandsLockedInterruptible();
+        return 1;
+    } // release lock
+}
+
+void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
+        InputState::CancelationOptions options, const char* reason) {
+    for (size_t i = 0; i < mConnectionsByReceiveFd.size(); i++) {
+        synthesizeCancelationEventsForConnectionLocked(
+                mConnectionsByReceiveFd.valueAt(i), options, reason);
+    }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
+        const sp<InputChannel>& channel, InputState::CancelationOptions options,
+        const char* reason) {
+    ssize_t index = getConnectionIndexLocked(channel);
+    if (index >= 0) {
+        synthesizeCancelationEventsForConnectionLocked(
+                mConnectionsByReceiveFd.valueAt(index), options, reason);
+    }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
+        const sp<Connection>& connection, InputState::CancelationOptions options,
+        const char* reason) {
+    nsecs_t currentTime = now();
+
+    mTempCancelationEvents.clear();
+    connection->inputState.synthesizeCancelationEvents(currentTime, & mAllocator,
+            mTempCancelationEvents, options);
+
+    if (! mTempCancelationEvents.isEmpty()
+            && connection->status != Connection::STATUS_BROKEN) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync "
+                "with reality: %s, options=%d.",
+                connection->getInputChannelName(), mTempCancelationEvents.size(), reason, options);
+#endif
+        for (size_t i = 0; i < mTempCancelationEvents.size(); i++) {
+            EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i);
+            switch (cancelationEventEntry->type) {
+            case EventEntry::TYPE_KEY:
+                logOutboundKeyDetailsLocked("cancel - ",
+                        static_cast<KeyEntry*>(cancelationEventEntry));
+                break;
+            case EventEntry::TYPE_MOTION:
+                logOutboundMotionDetailsLocked("cancel - ",
+                        static_cast<MotionEntry*>(cancelationEventEntry));
+                break;
+            }
+
+            int32_t xOffset, yOffset;
+            const InputWindow* window = getWindowLocked(connection->inputChannel);
+            if (window) {
+                xOffset = -window->frameLeft;
+                yOffset = -window->frameTop;
+            } else {
+                xOffset = 0;
+                yOffset = 0;
+            }
+
+            DispatchEntry* cancelationDispatchEntry =
+                    mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref
+                    0, xOffset, yOffset);
+            connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+
+            mAllocator.releaseEventEntry(cancelationEventEntry);
+        }
+
+        if (!connection->outboundQueue.headSentinel.next->inProgress) {
+            startDispatchCycleLocked(currentTime, connection);
+        }
+    }
+}
+
+InputDispatcher::MotionEntry*
+InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) {
+    assert(pointerIds.value != 0);
+
+    uint32_t splitPointerIndexMap[MAX_POINTERS];
+    int32_t splitPointerIds[MAX_POINTERS];
+    PointerCoords splitPointerCoords[MAX_POINTERS];
+
+    uint32_t originalPointerCount = originalMotionEntry->pointerCount;
+    uint32_t splitPointerCount = 0;
+
+    for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
+            originalPointerIndex++) {
+        int32_t pointerId = uint32_t(originalMotionEntry->pointerIds[originalPointerIndex]);
+        if (pointerIds.hasBit(pointerId)) {
+            splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
+            splitPointerIds[splitPointerCount] = pointerId;
+            splitPointerCoords[splitPointerCount] =
+                    originalMotionEntry->firstSample.pointerCoords[originalPointerIndex];
+            splitPointerCount += 1;
+        }
+    }
+    assert(splitPointerCount == pointerIds.count());
+
+    int32_t action = originalMotionEntry->action;
+    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+            || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+        int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+        int32_t pointerId = originalMotionEntry->pointerIds[originalPointerIndex];
+        if (pointerIds.hasBit(pointerId)) {
+            if (pointerIds.count() == 1) {
+                // The first/last pointer went down/up.
+                action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+                        ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+            } else {
+                // A secondary pointer went down/up.
+                uint32_t splitPointerIndex = 0;
+                while (pointerId != splitPointerIds[splitPointerIndex]) {
+                    splitPointerIndex += 1;
+                }
+                action = maskedAction | (splitPointerIndex
+                        << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+            }
+        } else {
+            // An unrelated pointer changed.
+            action = AMOTION_EVENT_ACTION_MOVE;
+        }
+    }
+
+    MotionEntry* splitMotionEntry = mAllocator.obtainMotionEntry(
+            originalMotionEntry->eventTime,
+            originalMotionEntry->deviceId,
+            originalMotionEntry->source,
+            originalMotionEntry->policyFlags,
+            action,
+            originalMotionEntry->flags,
+            originalMotionEntry->metaState,
+            originalMotionEntry->edgeFlags,
+            originalMotionEntry->xPrecision,
+            originalMotionEntry->yPrecision,
+            originalMotionEntry->downTime,
+            splitPointerCount, splitPointerIds, splitPointerCoords);
+
+    for (MotionSample* originalMotionSample = originalMotionEntry->firstSample.next;
+            originalMotionSample != NULL; originalMotionSample = originalMotionSample->next) {
+        for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount;
+                splitPointerIndex++) {
+            uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex];
+            splitPointerCoords[splitPointerIndex] =
+                    originalMotionSample->pointerCoords[originalPointerIndex];
+        }
+
+        mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime,
+                splitPointerCoords);
+    }
+
+    return splitMotionEntry;
+}
+
+void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime);
+#endif
+
+    bool needWake;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
+        needWake = enqueueInboundEventLocked(newEntry);
+    } // release lock
+
+    if (needWake) {
+        mLooper->wake();
+    }
+}
+
+void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
+        uint32_t policyFlags, int32_t action, int32_t flags,
+        int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyKey - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, action=0x%x, "
+            "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
+            eventTime, deviceId, source, policyFlags, action, flags,
+            keyCode, scanCode, metaState, downTime);
+#endif
+    if (! validateKeyEvent(action)) {
+        return;
+    }
+
+    if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
+        policyFlags |= POLICY_FLAG_VIRTUAL;
+        flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+    }
+
+    policyFlags |= POLICY_FLAG_TRUSTED;
+
+    KeyEvent event;
+    event.initialize(deviceId, source, action, flags, keyCode, scanCode,
+            metaState, 0, downTime, eventTime);
+
+    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
+
+    if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+        flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+    }
+
+    bool needWake;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        int32_t repeatCount = 0;
+        KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,
+                deviceId, source, policyFlags, action, flags, keyCode, scanCode,
+                metaState, repeatCount, downTime);
+
+        needWake = enqueueInboundEventLocked(newEntry);
+    } // release lock
+
+    if (needWake) {
+        mLooper->wake();
+    }
+}
+
+void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
+        uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,
+        uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+        float xPrecision, float yPrecision, nsecs_t downTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyMotion - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, metaState=0x%x, edgeFlags=0x%x, "
+            "xPrecision=%f, yPrecision=%f, downTime=%lld",
+            eventTime, deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
+            xPrecision, yPrecision, downTime);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
+                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+                "orientation=%f",
+                i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
+                pointerCoords[i].pressure, pointerCoords[i].size,
+                pointerCoords[i].touchMajor, pointerCoords[i].touchMinor,
+                pointerCoords[i].toolMajor, pointerCoords[i].toolMinor,
+                pointerCoords[i].orientation);
+    }
+#endif
+    if (! validateMotionEvent(action, pointerCount, pointerIds)) {
+        return;
+    }
+
+    policyFlags |= POLICY_FLAG_TRUSTED;
+    mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);
+
+    bool needWake;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        // Attempt batching and streaming of move events.
+        if (action == AMOTION_EVENT_ACTION_MOVE) {
+            // BATCHING CASE
+            //
+            // Try to append a move sample to the tail of the inbound queue for this device.
+            // Give up if we encounter a non-move motion event for this device since that
+            // means we cannot append any new samples until a new motion event has started.
+            for (EventEntry* entry = mInboundQueue.tailSentinel.prev;
+                    entry != & mInboundQueue.headSentinel; entry = entry->prev) {
+                if (entry->type != EventEntry::TYPE_MOTION) {
+                    // Keep looking for motion events.
+                    continue;
+                }
+
+                MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+                if (motionEntry->deviceId != deviceId) {
+                    // Keep looking for this device.
+                    continue;
+                }
+
+                if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+                        || motionEntry->pointerCount != pointerCount
+                        || motionEntry->isInjected()) {
+                    // Last motion event in the queue for this device is not compatible for
+                    // appending new samples.  Stop here.
+                    goto NoBatchingOrStreaming;
+                }
+
+                // The last motion event is a move and is compatible for appending.
+                // Do the batching magic.
+                mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
+#if DEBUG_BATCHING
+                LOGD("Appended motion sample onto batch for most recent "
+                        "motion event for this device in the inbound queue.");
+#endif
+                return; // done!
+            }
+
+            // STREAMING CASE
+            //
+            // There is no pending motion event (of any kind) for this device in the inbound queue.
+            // Search the outbound queue for the current foreground targets to find a dispatched
+            // motion event that is still in progress.  If found, then, appen the new sample to
+            // that event and push it out to all current targets.  The logic in
+            // prepareDispatchCycleLocked takes care of the case where some targets may
+            // already have consumed the motion event by starting a new dispatch cycle if needed.
+            if (mCurrentInputTargetsValid) {
+                for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+                    const InputTarget& inputTarget = mCurrentInputTargets[i];
+                    if ((inputTarget.flags & InputTarget::FLAG_FOREGROUND) == 0) {
+                        // Skip non-foreground targets.  We only want to stream if there is at
+                        // least one foreground target whose dispatch is still in progress.
+                        continue;
+                    }
+
+                    ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+                    if (connectionIndex < 0) {
+                        // Connection must no longer be valid.
+                        continue;
+                    }
+
+                    sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+                    if (connection->outboundQueue.isEmpty()) {
+                        // This foreground target has an empty outbound queue.
+                        continue;
+                    }
+
+                    DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+                    if (! dispatchEntry->inProgress
+                            || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION
+                            || dispatchEntry->isSplit()) {
+                        // No motion event is being dispatched, or it is being split across
+                        // windows in which case we cannot stream.
+                        continue;
+                    }
+
+                    MotionEntry* motionEntry = static_cast<MotionEntry*>(
+                            dispatchEntry->eventEntry);
+                    if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+                            || motionEntry->deviceId != deviceId
+                            || motionEntry->pointerCount != pointerCount
+                            || motionEntry->isInjected()) {
+                        // The motion event is not compatible with this move.
+                        continue;
+                    }
+
+                    // Hurray!  This foreground target is currently dispatching a move event
+                    // that we can stream onto.  Append the motion sample and resume dispatch.
+                    mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
+#if DEBUG_BATCHING
+                    LOGD("Appended motion sample onto batch for most recently dispatched "
+                            "motion event for this device in the outbound queues.  "
+                            "Attempting to stream the motion sample.");
+#endif
+                    nsecs_t currentTime = now();
+                    dispatchEventToCurrentInputTargetsLocked(currentTime, motionEntry,
+                            true /*resumeWithAppendedMotionSample*/);
+
+                    runCommandsLockedInterruptible();
+                    return; // done!
+                }
+            }
+
+NoBatchingOrStreaming:;
+        }
+
+        // Just enqueue a new motion event.
+        MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
+                deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
+                xPrecision, yPrecision, downTime,
+                pointerCount, pointerIds, pointerCoords);
+
+        needWake = enqueueInboundEventLocked(newEntry);
+    } // release lock
+
+    if (needWake) {
+        mLooper->wake();
+    }
+}
+
+void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
+        uint32_t policyFlags) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifySwitch - switchCode=%d, switchValue=%d, policyFlags=0x%x",
+            switchCode, switchValue, policyFlags);
+#endif
+
+    policyFlags |= POLICY_FLAG_TRUSTED;
+    mPolicy->notifySwitch(when, switchCode, switchValue, policyFlags);
+}
+
+int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
+        int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+            "syncMode=%d, timeoutMillis=%d",
+            event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis);
+#endif
+
+    nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
+
+    uint32_t policyFlags = POLICY_FLAG_INJECTED;
+    if (hasInjectionPermission(injectorPid, injectorUid)) {
+        policyFlags |= POLICY_FLAG_TRUSTED;
+    }
+
+    EventEntry* injectedEntry;
+    switch (event->getType()) {
+    case AINPUT_EVENT_TYPE_KEY: {
+        const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+        int32_t action = keyEvent->getAction();
+        if (! validateKeyEvent(action)) {
+            return INPUT_EVENT_INJECTION_FAILED;
+        }
+
+        int32_t flags = keyEvent->getFlags();
+        if (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY) {
+            policyFlags |= POLICY_FLAG_VIRTUAL;
+        }
+
+        mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
+
+        if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+            flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+        }
+
+        mLock.lock();
+        injectedEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
+                keyEvent->getDeviceId(), keyEvent->getSource(),
+                policyFlags, action, flags,
+                keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
+                keyEvent->getRepeatCount(), keyEvent->getDownTime());
+        break;
+    }
+
+    case AINPUT_EVENT_TYPE_MOTION: {
+        const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+        int32_t action = motionEvent->getAction();
+        size_t pointerCount = motionEvent->getPointerCount();
+        const int32_t* pointerIds = motionEvent->getPointerIds();
+        if (! validateMotionEvent(action, pointerCount, pointerIds)) {
+            return INPUT_EVENT_INJECTION_FAILED;
+        }
+
+        nsecs_t eventTime = motionEvent->getEventTime();
+        mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);
+
+        mLock.lock();
+        const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
+        const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
+        MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
+                motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+                action, motionEvent->getFlags(),
+                motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
+                motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+                motionEvent->getDownTime(), uint32_t(pointerCount),
+                pointerIds, samplePointerCoords);
+        for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
+            sampleEventTimes += 1;
+            samplePointerCoords += pointerCount;
+            mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords);
+        }
+        injectedEntry = motionEntry;
+        break;
+    }
+
+    default:
+        LOGW("Cannot inject event of type %d", event->getType());
+        return INPUT_EVENT_INJECTION_FAILED;
+    }
+
+    InjectionState* injectionState = mAllocator.obtainInjectionState(injectorPid, injectorUid);
+    if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+        injectionState->injectionIsAsync = true;
+    }
+
+    injectionState->refCount += 1;
+    injectedEntry->injectionState = injectionState;
+
+    bool needWake = enqueueInboundEventLocked(injectedEntry);
+    mLock.unlock();
+
+    if (needWake) {
+        mLooper->wake();
+    }
+
+    int32_t injectionResult;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+            injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+        } else {
+            for (;;) {
+                injectionResult = injectionState->injectionResult;
+                if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
+                    break;
+                }
+
+                nsecs_t remainingTimeout = endTime - now();
+                if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+                    LOGD("injectInputEvent - Timed out waiting for injection result "
+                            "to become available.");
+#endif
+                    injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+                    break;
+                }
+
+                mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
+            }
+
+            if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
+                    && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
+                while (injectionState->pendingForegroundDispatches != 0) {
+#if DEBUG_INJECTION
+                    LOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+                            injectionState->pendingForegroundDispatches);
+#endif
+                    nsecs_t remainingTimeout = endTime - now();
+                    if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+                    LOGD("injectInputEvent - Timed out waiting for pending foreground "
+                            "dispatches to finish.");
+#endif
+                        injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+                        break;
+                    }
+
+                    mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout);
+                }
+            }
+        }
+
+        mAllocator.releaseInjectionState(injectionState);
+    } // release lock
+
+#if DEBUG_INJECTION
+    LOGD("injectInputEvent - Finished with result %d.  "
+            "injectorPid=%d, injectorUid=%d",
+            injectionResult, injectorPid, injectorUid);
+#endif
+
+    return injectionResult;
+}
+
+bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) {
+    return injectorUid == 0
+            || mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+}
+
+void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState) {
+#if DEBUG_INJECTION
+        LOGD("Setting input event injection result to %d.  "
+                "injectorPid=%d, injectorUid=%d",
+                 injectionResult, injectionState->injectorPid, injectionState->injectorUid);
+#endif
+
+        if (injectionState->injectionIsAsync) {
+            // Log the outcome since the injector did not wait for the injection result.
+            switch (injectionResult) {
+            case INPUT_EVENT_INJECTION_SUCCEEDED:
+                LOGV("Asynchronous input event injection succeeded.");
+                break;
+            case INPUT_EVENT_INJECTION_FAILED:
+                LOGW("Asynchronous input event injection failed.");
+                break;
+            case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+                LOGW("Asynchronous input event injection permission denied.");
+                break;
+            case INPUT_EVENT_INJECTION_TIMED_OUT:
+                LOGW("Asynchronous input event injection timed out.");
+                break;
+            }
+        }
+
+        injectionState->injectionResult = injectionResult;
+        mInjectionResultAvailableCondition.broadcast();
+    }
+}
+
+void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState) {
+        injectionState->pendingForegroundDispatches += 1;
+    }
+}
+
+void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+    InjectionState* injectionState = entry->injectionState;
+    if (injectionState) {
+        injectionState->pendingForegroundDispatches -= 1;
+
+        if (injectionState->pendingForegroundDispatches == 0) {
+            mInjectionSyncFinishedCondition.broadcast();
+        }
+    }
+}
+
+const InputWindow* InputDispatcher::getWindowLocked(const sp<InputChannel>& inputChannel) {
+    for (size_t i = 0; i < mWindows.size(); i++) {
+        const InputWindow* window = & mWindows[i];
+        if (window->inputChannel == inputChannel) {
+            return window;
+        }
+    }
+    return NULL;
+}
+
+void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {
+#if DEBUG_FOCUS
+    LOGD("setInputWindows");
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        // Clear old window pointers.
+        sp<InputChannel> oldFocusedWindowChannel;
+        if (mFocusedWindow) {
+            oldFocusedWindowChannel = mFocusedWindow->inputChannel;
+            mFocusedWindow = NULL;
+        }
+
+        mWindows.clear();
+
+        // Loop over new windows and rebuild the necessary window pointers for
+        // tracking focus and touch.
+        mWindows.appendVector(inputWindows);
+
+        size_t numWindows = mWindows.size();
+        for (size_t i = 0; i < numWindows; i++) {
+            const InputWindow* window = & mWindows.itemAt(i);
+            if (window->hasFocus) {
+                mFocusedWindow = window;
+                break;
+            }
+        }
+
+        if (oldFocusedWindowChannel != NULL) {
+            if (!mFocusedWindow || oldFocusedWindowChannel != mFocusedWindow->inputChannel) {
+#if DEBUG_FOCUS
+                LOGD("Focus left window: %s",
+                        oldFocusedWindowChannel->getName().string());
+#endif
+                synthesizeCancelationEventsForInputChannelLocked(oldFocusedWindowChannel,
+                        InputState::CANCEL_NON_POINTER_EVENTS, "focus left window");
+                oldFocusedWindowChannel.clear();
+            }
+        }
+        if (mFocusedWindow && oldFocusedWindowChannel == NULL) {
+#if DEBUG_FOCUS
+            LOGD("Focus entered window: %s",
+                    mFocusedWindow->inputChannel->getName().string());
+#endif
+        }
+
+        for (size_t i = 0; i < mTouchState.windows.size(); ) {
+            TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i);
+            const InputWindow* window = getWindowLocked(touchedWindow.channel);
+            if (window) {
+                touchedWindow.window = window;
+                i += 1;
+            } else {
+#if DEBUG_FOCUS
+                LOGD("Touched window was removed: %s", touchedWindow.channel->getName().string());
+#endif
+                synthesizeCancelationEventsForInputChannelLocked(touchedWindow.channel,
+                        InputState::CANCEL_POINTER_EVENTS, "touched window was removed");
+                mTouchState.windows.removeAt(i);
+            }
+        }
+
+#if DEBUG_FOCUS
+        //logDispatchStateLocked();
+#endif
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
+void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) {
+#if DEBUG_FOCUS
+    LOGD("setFocusedApplication");
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        releaseFocusedApplicationLocked();
+
+        if (inputApplication) {
+            mFocusedApplicationStorage = *inputApplication;
+            mFocusedApplication = & mFocusedApplicationStorage;
+        }
+
+#if DEBUG_FOCUS
+        //logDispatchStateLocked();
+#endif
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
+void InputDispatcher::releaseFocusedApplicationLocked() {
+    if (mFocusedApplication) {
+        mFocusedApplication = NULL;
+        mFocusedApplicationStorage.handle.clear();
+    }
+}
+
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+    LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
+#endif
+
+    bool changed;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+            if (mDispatchFrozen && !frozen) {
+                resetANRTimeoutsLocked();
+            }
+
+            if (mDispatchEnabled && !enabled) {
+                resetAndDropEverythingLocked("dispatcher is being disabled");
+            }
+
+            mDispatchEnabled = enabled;
+            mDispatchFrozen = frozen;
+            changed = true;
+        } else {
+            changed = false;
+        }
+
+#if DEBUG_FOCUS
+        //logDispatchStateLocked();
+#endif
+    } // release lock
+
+    if (changed) {
+        // Wake up poll loop since it may need to make new input dispatching choices.
+        mLooper->wake();
+    }
+}
+
+bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
+        const sp<InputChannel>& toChannel) {
+#if DEBUG_FOCUS
+    LOGD("transferTouchFocus: fromChannel=%s, toChannel=%s",
+            fromChannel->getName().string(), toChannel->getName().string());
+#endif
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        const InputWindow* fromWindow = getWindowLocked(fromChannel);
+        const InputWindow* toWindow = getWindowLocked(toChannel);
+        if (! fromWindow || ! toWindow) {
+#if DEBUG_FOCUS
+            LOGD("Cannot transfer focus because from or to window not found.");
+#endif
+            return false;
+        }
+        if (fromWindow == toWindow) {
+#if DEBUG_FOCUS
+            LOGD("Trivial transfer to same window.");
+#endif
+            return true;
+        }
+
+        bool found = false;
+        for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTouchState.windows[i];
+            if (touchedWindow.window == fromWindow) {
+                int32_t oldTargetFlags = touchedWindow.targetFlags;
+                BitSet32 pointerIds = touchedWindow.pointerIds;
+
+                mTouchState.windows.removeAt(i);
+
+                int32_t newTargetFlags = oldTargetFlags
+                        & (InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_SPLIT);
+                mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds);
+
+                found = true;
+                break;
+            }
+        }
+
+        if (! found) {
+#if DEBUG_FOCUS
+            LOGD("Focus transfer failed because from window did not have focus.");
+#endif
+            return false;
+        }
+
+        ssize_t fromConnectionIndex = getConnectionIndexLocked(fromChannel);
+        ssize_t toConnectionIndex = getConnectionIndexLocked(toChannel);
+        if (fromConnectionIndex >= 0 && toConnectionIndex >= 0) {
+            sp<Connection> fromConnection = mConnectionsByReceiveFd.valueAt(fromConnectionIndex);
+            sp<Connection> toConnection = mConnectionsByReceiveFd.valueAt(toConnectionIndex);
+
+            fromConnection->inputState.copyPointerStateTo(toConnection->inputState);
+            synthesizeCancelationEventsForConnectionLocked(fromConnection,
+                    InputState::CANCEL_POINTER_EVENTS,
+                    "transferring touch focus from this window to another window");
+        }
+
+#if DEBUG_FOCUS
+        logDispatchStateLocked();
+#endif
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+    return true;
+}
+
+void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
+#if DEBUG_FOCUS
+    LOGD("Resetting and dropping all events (%s).", reason);
+#endif
+
+    synthesizeCancelationEventsForAllConnectionsLocked(InputState::CANCEL_ALL_EVENTS, reason);
+
+    resetKeyRepeatLocked();
+    releasePendingEventLocked();
+    drainInboundQueueLocked();
+    resetTargetsLocked();
+
+    mTouchState.reset();
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+    String8 dump;
+    dumpDispatchStateLocked(dump);
+
+    char* text = dump.lockBuffer(dump.size());
+    char* start = text;
+    while (*start != '\0') {
+        char* end = strchr(start, '\n');
+        if (*end == '\n') {
+            *(end++) = '\0';
+        }
+        LOGD("%s", start);
+        start = end;
+    }
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+    dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
+    dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
+
+    if (mFocusedApplication) {
+        dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+                mFocusedApplication->name.string(),
+                mFocusedApplication->dispatchingTimeout / 1000000.0);
+    } else {
+        dump.append(INDENT "FocusedApplication: <null>\n");
+    }
+    dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
+            mFocusedWindow != NULL ? mFocusedWindow->name.string() : "<null>");
+
+    dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down));
+    dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split));
+    if (!mTouchState.windows.isEmpty()) {
+        dump.append(INDENT "TouchedWindows:\n");
+        for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+            const TouchedWindow& touchedWindow = mTouchState.windows[i];
+            dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
+                    i, touchedWindow.window->name.string(), touchedWindow.pointerIds.value,
+                    touchedWindow.targetFlags);
+        }
+    } else {
+        dump.append(INDENT "TouchedWindows: <none>\n");
+    }
+
+    if (!mWindows.isEmpty()) {
+        dump.append(INDENT "Windows:\n");
+        for (size_t i = 0; i < mWindows.size(); i++) {
+            const InputWindow& window = mWindows[i];
+            dump.appendFormat(INDENT2 "%d: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
+                    "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+                    "frame=[%d,%d][%d,%d], "
+                    "visibleFrame=[%d,%d][%d,%d], "
+                    "touchableArea=[%d,%d][%d,%d], "
+                    "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+                    i, window.name.string(),
+                    toString(window.paused),
+                    toString(window.hasFocus),
+                    toString(window.hasWallpaper),
+                    toString(window.visible),
+                    toString(window.canReceiveKeys),
+                    window.layoutParamsFlags, window.layoutParamsType,
+                    window.layer,
+                    window.frameLeft, window.frameTop,
+                    window.frameRight, window.frameBottom,
+                    window.visibleFrameLeft, window.visibleFrameTop,
+                    window.visibleFrameRight, window.visibleFrameBottom,
+                    window.touchableAreaLeft, window.touchableAreaTop,
+                    window.touchableAreaRight, window.touchableAreaBottom,
+                    window.ownerPid, window.ownerUid,
+                    window.dispatchingTimeout / 1000000.0);
+        }
+    } else {
+        dump.append(INDENT "Windows: <none>\n");
+    }
+
+    if (!mMonitoringChannels.isEmpty()) {
+        dump.append(INDENT "MonitoringChannels:\n");
+        for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+            const sp<InputChannel>& channel = mMonitoringChannels[i];
+            dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string());
+        }
+    } else {
+        dump.append(INDENT "MonitoringChannels: <none>\n");
+    }
+
+    dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+
+    if (!mActiveConnections.isEmpty()) {
+        dump.append(INDENT "ActiveConnections:\n");
+        for (size_t i = 0; i < mActiveConnections.size(); i++) {
+            const Connection* connection = mActiveConnections[i];
+            dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u, "
+                    "inputState.isNeutral=%s\n",
+                    i, connection->getInputChannelName(), connection->getStatusLabel(),
+                    connection->outboundQueue.count(),
+                    toString(connection->inputState.isNeutral()));
+        }
+    } else {
+        dump.append(INDENT "ActiveConnections: <none>\n");
+    }
+
+    if (isAppSwitchPendingLocked()) {
+        dump.appendFormat(INDENT "AppSwitch: pending, due in %01.1fms\n",
+                (mAppSwitchDueTime - now()) / 1000000.0);
+    } else {
+        dump.append(INDENT "AppSwitch: not pending\n");
+    }
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {
+#if DEBUG_REGISTRATION
+    LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+            toString(monitor));
+#endif
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (getConnectionIndexLocked(inputChannel) >= 0) {
+            LOGW("Attempted to register already registered input channel '%s'",
+                    inputChannel->getName().string());
+            return BAD_VALUE;
+        }
+
+        sp<Connection> connection = new Connection(inputChannel);
+        status_t status = connection->initialize();
+        if (status) {
+            LOGE("Failed to initialize input publisher for input channel '%s', status=%d",
+                    inputChannel->getName().string(), status);
+            return status;
+        }
+
+        int32_t receiveFd = inputChannel->getReceivePipeFd();
+        mConnectionsByReceiveFd.add(receiveFd, connection);
+
+        if (monitor) {
+            mMonitoringChannels.push(inputChannel);
+        }
+
+        mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
+
+        runCommandsLockedInterruptible();
+    } // release lock
+    return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+#if DEBUG_REGISTRATION
+    LOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string());
+#endif
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+        if (connectionIndex < 0) {
+            LOGW("Attempted to unregister already unregistered input channel '%s'",
+                    inputChannel->getName().string());
+            return BAD_VALUE;
+        }
+
+        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
+
+        connection->status = Connection::STATUS_ZOMBIE;
+
+        for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+            if (mMonitoringChannels[i] == inputChannel) {
+                mMonitoringChannels.removeAt(i);
+                break;
+            }
+        }
+
+        mLooper->removeFd(inputChannel->getReceivePipeFd());
+
+        nsecs_t currentTime = now();
+        abortBrokenDispatchCycleLocked(currentTime, connection);
+
+        runCommandsLockedInterruptible();
+    } // release lock
+
+    // Wake the poll loop because removing the connection may have changed the current
+    // synchronization state.
+    mLooper->wake();
+    return OK;
+}
+
+ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
+    ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd());
+    if (connectionIndex >= 0) {
+        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        if (connection->inputChannel.get() == inputChannel.get()) {
+            return connectionIndex;
+        }
+    }
+
+    return -1;
+}
+
+void InputDispatcher::activateConnectionLocked(Connection* connection) {
+    for (size_t i = 0; i < mActiveConnections.size(); i++) {
+        if (mActiveConnections.itemAt(i) == connection) {
+            return;
+        }
+    }
+    mActiveConnections.add(connection);
+}
+
+void InputDispatcher::deactivateConnectionLocked(Connection* connection) {
+    for (size_t i = 0; i < mActiveConnections.size(); i++) {
+        if (mActiveConnections.itemAt(i) == connection) {
+            mActiveConnections.removeAt(i);
+            return;
+        }
+    }
+}
+
+void InputDispatcher::onDispatchCycleStartedLocked(
+        nsecs_t currentTime, const sp<Connection>& connection) {
+}
+
+void InputDispatcher::onDispatchCycleFinishedLocked(
+        nsecs_t currentTime, const sp<Connection>& connection, bool handled) {
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
+    commandEntry->connection = connection;
+    commandEntry->handled = handled;
+}
+
+void InputDispatcher::onDispatchCycleBrokenLocked(
+        nsecs_t currentTime, const sp<Connection>& connection) {
+    LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
+            connection->getInputChannelName());
+
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
+    commandEntry->connection = connection;
+}
+
+void InputDispatcher::onANRLocked(
+        nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+        nsecs_t eventTime, nsecs_t waitStartTime) {
+    LOGI("Application is not responding: %s.  "
+            "%01.1fms since event, %01.1fms since wait started",
+            getApplicationWindowLabelLocked(application, window).string(),
+            (currentTime - eventTime) / 1000000.0,
+            (currentTime - waitStartTime) / 1000000.0);
+
+    CommandEntry* commandEntry = postCommandLocked(
+            & InputDispatcher::doNotifyANRLockedInterruptible);
+    if (application) {
+        commandEntry->inputApplicationHandle = application->handle;
+    }
+    if (window) {
+        commandEntry->inputChannel = window->inputChannel;
+    }
+}
+
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+        CommandEntry* commandEntry) {
+    mLock.unlock();
+
+    mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+    mLock.lock();
+}
+
+void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
+        CommandEntry* commandEntry) {
+    sp<Connection> connection = commandEntry->connection;
+
+    if (connection->status != Connection::STATUS_ZOMBIE) {
+        mLock.unlock();
+
+        mPolicy->notifyInputChannelBroken(connection->inputChannel);
+
+        mLock.lock();
+    }
+}
+
+void InputDispatcher::doNotifyANRLockedInterruptible(
+        CommandEntry* commandEntry) {
+    mLock.unlock();
+
+    nsecs_t newTimeout = mPolicy->notifyANR(
+            commandEntry->inputApplicationHandle, commandEntry->inputChannel);
+
+    mLock.lock();
+
+    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputChannel);
+}
+
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
+        CommandEntry* commandEntry) {
+    KeyEntry* entry = commandEntry->keyEntry;
+
+    KeyEvent event;
+    initializeKeyEvent(&event, entry);
+
+    mLock.unlock();
+
+    bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputChannel,
+            &event, entry->policyFlags);
+
+    mLock.lock();
+
+    entry->interceptKeyResult = consumed
+            ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP
+            : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+    mAllocator.releaseKeyEntry(entry);
+}
+
+void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
+        CommandEntry* commandEntry) {
+    sp<Connection> connection = commandEntry->connection;
+    bool handled = commandEntry->handled;
+
+    if (!connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+        if (dispatchEntry->inProgress
+                && dispatchEntry->hasForegroundTarget()
+                && dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
+            KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+            if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
+                if (handled) {
+                    // If the application handled a non-fallback key, then immediately
+                    // cancel all fallback keys previously dispatched to the application.
+                    // This behavior will prevent chording with fallback keys (so they cannot
+                    // be used as modifiers) but it will ensure that fallback keys do not
+                    // get stuck.  This takes care of the case where the application does not handle
+                    // the original DOWN so we generate a fallback DOWN but it does handle
+                    // the original UP in which case we would not generate the fallback UP.
+                    synthesizeCancelationEventsForConnectionLocked(connection,
+                            InputState::CANCEL_FALLBACK_EVENTS,
+                            "application handled a non-fallback event, canceling all fallback events");
+                } else {
+                    // If the application did not handle a non-fallback key, then ask
+                    // the policy what to do with it.  We might generate a fallback key
+                    // event here.
+                    KeyEvent event;
+                    initializeKeyEvent(&event, keyEntry);
+
+                    mLock.unlock();
+
+                    bool fallback = mPolicy->dispatchUnhandledKey(connection->inputChannel,
+                            &event, keyEntry->policyFlags, &event);
+
+                    mLock.lock();
+
+                    if (connection->status != Connection::STATUS_NORMAL) {
+                        return;
+                    }
+
+                    assert(connection->outboundQueue.headSentinel.next == dispatchEntry);
+
+                    if (fallback) {
+                        // Restart the dispatch cycle using the fallback key.
+                        keyEntry->eventTime = event.getEventTime();
+                        keyEntry->deviceId = event.getDeviceId();
+                        keyEntry->source = event.getSource();
+                        keyEntry->flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
+                        keyEntry->keyCode = event.getKeyCode();
+                        keyEntry->scanCode = event.getScanCode();
+                        keyEntry->metaState = event.getMetaState();
+                        keyEntry->repeatCount = event.getRepeatCount();
+                        keyEntry->downTime = event.getDownTime();
+                        keyEntry->syntheticRepeat = false;
+
+                        dispatchEntry->inProgress = false;
+                        startDispatchCycleLocked(now(), connection);
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    startNextDispatchCycleLocked(now(), connection);
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+    mLock.unlock();
+
+    mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType);
+
+    mLock.lock();
+}
+
+void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) {
+    event->initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+            entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+            entry->downTime, entry->eventTime);
+}
+
+void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+        int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
+    // TODO Write some statistics about how long we spend waiting.
+}
+
+void InputDispatcher::dump(String8& dump) {
+    dump.append("Input Dispatcher State:\n");
+    dumpDispatchStateLocked(dump);
+}
+
+
+// --- InputDispatcher::Queue ---
+
+template <typename T>
+uint32_t InputDispatcher::Queue<T>::count() const {
+    uint32_t result = 0;
+    for (const T* entry = headSentinel.next; entry != & tailSentinel; entry = entry->next) {
+        result += 1;
+    }
+    return result;
+}
+
+
+// --- InputDispatcher::Allocator ---
+
+InputDispatcher::Allocator::Allocator() {
+}
+
+InputDispatcher::InjectionState*
+InputDispatcher::Allocator::obtainInjectionState(int32_t injectorPid, int32_t injectorUid) {
+    InjectionState* injectionState = mInjectionStatePool.alloc();
+    injectionState->refCount = 1;
+    injectionState->injectorPid = injectorPid;
+    injectionState->injectorUid = injectorUid;
+    injectionState->injectionIsAsync = false;
+    injectionState->injectionResult = INPUT_EVENT_INJECTION_PENDING;
+    injectionState->pendingForegroundDispatches = 0;
+    return injectionState;
+}
+
+void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type,
+        nsecs_t eventTime, uint32_t policyFlags) {
+    entry->type = type;
+    entry->refCount = 1;
+    entry->dispatchInProgress = false;
+    entry->eventTime = eventTime;
+    entry->policyFlags = policyFlags;
+    entry->injectionState = NULL;
+}
+
+void InputDispatcher::Allocator::releaseEventEntryInjectionState(EventEntry* entry) {
+    if (entry->injectionState) {
+        releaseInjectionState(entry->injectionState);
+        entry->injectionState = NULL;
+    }
+}
+
+InputDispatcher::ConfigurationChangedEntry*
+InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) {
+    ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
+    initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime, 0);
+    return entry;
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t eventTime,
+        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
+        int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+        int32_t repeatCount, nsecs_t downTime) {
+    KeyEntry* entry = mKeyEntryPool.alloc();
+    initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime, policyFlags);
+
+    entry->deviceId = deviceId;
+    entry->source = source;
+    entry->action = action;
+    entry->flags = flags;
+    entry->keyCode = keyCode;
+    entry->scanCode = scanCode;
+    entry->metaState = metaState;
+    entry->repeatCount = repeatCount;
+    entry->downTime = downTime;
+    entry->syntheticRepeat = false;
+    entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+    return entry;
+}
+
+InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
+        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
+        int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
+        nsecs_t downTime, uint32_t pointerCount,
+        const int32_t* pointerIds, const PointerCoords* pointerCoords) {
+    MotionEntry* entry = mMotionEntryPool.alloc();
+    initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime, policyFlags);
+
+    entry->eventTime = eventTime;
+    entry->deviceId = deviceId;
+    entry->source = source;
+    entry->action = action;
+    entry->flags = flags;
+    entry->metaState = metaState;
+    entry->edgeFlags = edgeFlags;
+    entry->xPrecision = xPrecision;
+    entry->yPrecision = yPrecision;
+    entry->downTime = downTime;
+    entry->pointerCount = pointerCount;
+    entry->firstSample.eventTime = eventTime;
+    entry->firstSample.next = NULL;
+    entry->lastSample = & entry->firstSample;
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        entry->pointerIds[i] = pointerIds[i];
+        entry->firstSample.pointerCoords[i] = pointerCoords[i];
+    }
+    return entry;
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
+        EventEntry* eventEntry,
+        int32_t targetFlags, float xOffset, float yOffset) {
+    DispatchEntry* entry = mDispatchEntryPool.alloc();
+    entry->eventEntry = eventEntry;
+    eventEntry->refCount += 1;
+    entry->targetFlags = targetFlags;
+    entry->xOffset = xOffset;
+    entry->yOffset = yOffset;
+    entry->inProgress = false;
+    entry->headMotionSample = NULL;
+    entry->tailMotionSample = NULL;
+    return entry;
+}
+
+InputDispatcher::CommandEntry* InputDispatcher::Allocator::obtainCommandEntry(Command command) {
+    CommandEntry* entry = mCommandEntryPool.alloc();
+    entry->command = command;
+    return entry;
+}
+
+void InputDispatcher::Allocator::releaseInjectionState(InjectionState* injectionState) {
+    injectionState->refCount -= 1;
+    if (injectionState->refCount == 0) {
+        mInjectionStatePool.free(injectionState);
+    } else {
+        assert(injectionState->refCount > 0);
+    }
+}
+
+void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) {
+    switch (entry->type) {
+    case EventEntry::TYPE_CONFIGURATION_CHANGED:
+        releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry));
+        break;
+    case EventEntry::TYPE_KEY:
+        releaseKeyEntry(static_cast<KeyEntry*>(entry));
+        break;
+    case EventEntry::TYPE_MOTION:
+        releaseMotionEntry(static_cast<MotionEntry*>(entry));
+        break;
+    default:
+        assert(false);
+        break;
+    }
+}
+
+void InputDispatcher::Allocator::releaseConfigurationChangedEntry(
+        ConfigurationChangedEntry* entry) {
+    entry->refCount -= 1;
+    if (entry->refCount == 0) {
+        releaseEventEntryInjectionState(entry);
+        mConfigurationChangeEntryPool.free(entry);
+    } else {
+        assert(entry->refCount > 0);
+    }
+}
+
+void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) {
+    entry->refCount -= 1;
+    if (entry->refCount == 0) {
+        releaseEventEntryInjectionState(entry);
+        mKeyEntryPool.free(entry);
+    } else {
+        assert(entry->refCount > 0);
+    }
+}
+
+void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) {
+    entry->refCount -= 1;
+    if (entry->refCount == 0) {
+        releaseEventEntryInjectionState(entry);
+        for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) {
+            MotionSample* next = sample->next;
+            mMotionSamplePool.free(sample);
+            sample = next;
+        }
+        mMotionEntryPool.free(entry);
+    } else {
+        assert(entry->refCount > 0);
+    }
+}
+
+void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) {
+    releaseEventEntry(entry->eventEntry);
+    mDispatchEntryPool.free(entry);
+}
+
+void InputDispatcher::Allocator::releaseCommandEntry(CommandEntry* entry) {
+    mCommandEntryPool.free(entry);
+}
+
+void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
+        nsecs_t eventTime, const PointerCoords* pointerCoords) {
+    MotionSample* sample = mMotionSamplePool.alloc();
+    sample->eventTime = eventTime;
+    uint32_t pointerCount = motionEntry->pointerCount;
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        sample->pointerCoords[i] = pointerCoords[i];
+    }
+
+    sample->next = NULL;
+    motionEntry->lastSample->next = sample;
+    motionEntry->lastSample = sample;
+}
+
+void InputDispatcher::Allocator::recycleKeyEntry(KeyEntry* keyEntry) {
+    releaseEventEntryInjectionState(keyEntry);
+
+    keyEntry->dispatchInProgress = false;
+    keyEntry->syntheticRepeat = false;
+    keyEntry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+}
+
+
+// --- InputDispatcher::MotionEntry ---
+
+uint32_t InputDispatcher::MotionEntry::countSamples() const {
+    uint32_t count = 1;
+    for (MotionSample* sample = firstSample.next; sample != NULL; sample = sample->next) {
+        count += 1;
+    }
+    return count;
+}
+
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+    return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent(
+        const EventEntry* entry) {
+    switch (entry->type) {
+    case EventEntry::TYPE_KEY:
+        return trackKey(static_cast<const KeyEntry*>(entry));
+
+    case EventEntry::TYPE_MOTION:
+        return trackMotion(static_cast<const MotionEntry*>(entry));
+
+    default:
+        return CONSISTENT;
+    }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey(
+        const KeyEntry* entry) {
+    int32_t action = entry->action;
+    for (size_t i = 0; i < mKeyMementos.size(); i++) {
+        KeyMemento& memento = mKeyMementos.editItemAt(i);
+        if (memento.deviceId == entry->deviceId
+                && memento.source == entry->source
+                && memento.keyCode == entry->keyCode
+                && memento.scanCode == entry->scanCode) {
+            switch (action) {
+            case AKEY_EVENT_ACTION_UP:
+                mKeyMementos.removeAt(i);
+                return CONSISTENT;
+
+            case AKEY_EVENT_ACTION_DOWN:
+                return TOLERABLE;
+
+            default:
+                return BROKEN;
+            }
+        }
+    }
+
+    switch (action) {
+    case AKEY_EVENT_ACTION_DOWN: {
+        mKeyMementos.push();
+        KeyMemento& memento = mKeyMementos.editTop();
+        memento.deviceId = entry->deviceId;
+        memento.source = entry->source;
+        memento.keyCode = entry->keyCode;
+        memento.scanCode = entry->scanCode;
+        memento.flags = entry->flags;
+        memento.downTime = entry->downTime;
+        return CONSISTENT;
+    }
+
+    default:
+        return BROKEN;
+    }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion(
+        const MotionEntry* entry) {
+    int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK;
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        MotionMemento& memento = mMotionMementos.editItemAt(i);
+        if (memento.deviceId == entry->deviceId
+                && memento.source == entry->source) {
+            switch (action) {
+            case AMOTION_EVENT_ACTION_UP:
+            case AMOTION_EVENT_ACTION_CANCEL:
+                mMotionMementos.removeAt(i);
+                return CONSISTENT;
+
+            case AMOTION_EVENT_ACTION_DOWN:
+                return TOLERABLE;
+
+            case AMOTION_EVENT_ACTION_POINTER_DOWN:
+                if (entry->pointerCount == memento.pointerCount + 1) {
+                    memento.setPointers(entry);
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            case AMOTION_EVENT_ACTION_POINTER_UP:
+                if (entry->pointerCount == memento.pointerCount - 1) {
+                    memento.setPointers(entry);
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            case AMOTION_EVENT_ACTION_MOVE:
+                if (entry->pointerCount == memento.pointerCount) {
+                    return CONSISTENT;
+                }
+                return BROKEN;
+
+            default:
+                return BROKEN;
+            }
+        }
+    }
+
+    switch (action) {
+    case AMOTION_EVENT_ACTION_DOWN: {
+        mMotionMementos.push();
+        MotionMemento& memento = mMotionMementos.editTop();
+        memento.deviceId = entry->deviceId;
+        memento.source = entry->source;
+        memento.xPrecision = entry->xPrecision;
+        memento.yPrecision = entry->yPrecision;
+        memento.downTime = entry->downTime;
+        memento.setPointers(entry);
+        return CONSISTENT;
+    }
+
+    default:
+        return BROKEN;
+    }
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+    pointerCount = entry->pointerCount;
+    for (uint32_t i = 0; i < entry->pointerCount; i++) {
+        pointerIds[i] = entry->pointerIds[i];
+        pointerCoords[i] = entry->lastSample->pointerCoords[i];
+    }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
+        Allocator* allocator, Vector<EventEntry*>& outEvents,
+        CancelationOptions options) {
+    for (size_t i = 0; i < mKeyMementos.size(); ) {
+        const KeyMemento& memento = mKeyMementos.itemAt(i);
+        if (shouldCancelKey(memento, options)) {
+            outEvents.push(allocator->obtainKeyEntry(currentTime,
+                    memento.deviceId, memento.source, 0,
+                    AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
+                    memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
+            mKeyMementos.removeAt(i);
+        } else {
+            i += 1;
+        }
+    }
+
+    for (size_t i = 0; i < mMotionMementos.size(); ) {
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
+        if (shouldCancelMotion(memento, options)) {
+            outEvents.push(allocator->obtainMotionEntry(currentTime,
+                    memento.deviceId, memento.source, 0,
+                    AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
+                    memento.xPrecision, memento.yPrecision, memento.downTime,
+                    memento.pointerCount, memento.pointerIds, memento.pointerCoords));
+            mMotionMementos.removeAt(i);
+        } else {
+            i += 1;
+        }
+    }
+}
+
+void InputDispatcher::InputState::clear() {
+    mKeyMementos.clear();
+    mMotionMementos.clear();
+}
+
+void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const {
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
+        if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+            for (size_t j = 0; j < other.mMotionMementos.size(); ) {
+                const MotionMemento& otherMemento = other.mMotionMementos.itemAt(j);
+                if (memento.deviceId == otherMemento.deviceId
+                        && memento.source == otherMemento.source) {
+                    other.mMotionMementos.removeAt(j);
+                } else {
+                    j += 1;
+                }
+            }
+            other.mMotionMementos.push(memento);
+        }
+    }
+}
+
+bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento,
+        CancelationOptions options) {
+    switch (options) {
+    case CANCEL_ALL_EVENTS:
+    case CANCEL_NON_POINTER_EVENTS:
+        return true;
+    case CANCEL_FALLBACK_EVENTS:
+        return memento.flags & AKEY_EVENT_FLAG_FALLBACK;
+    default:
+        return false;
+    }
+}
+
+bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento,
+        CancelationOptions options) {
+    switch (options) {
+    case CANCEL_ALL_EVENTS:
+        return true;
+    case CANCEL_POINTER_EVENTS:
+        return memento.source & AINPUT_SOURCE_CLASS_POINTER;
+    case CANCEL_NON_POINTER_EVENTS:
+        return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+    default:
+        return false;
+    }
+}
+
+
+// --- InputDispatcher::Connection ---
+
+InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
+        status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel),
+        lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) {
+}
+
+InputDispatcher::Connection::~Connection() {
+}
+
+status_t InputDispatcher::Connection::initialize() {
+    return inputPublisher.initialize();
+}
+
+const char* InputDispatcher::Connection::getStatusLabel() const {
+    switch (status) {
+    case STATUS_NORMAL:
+        return "NORMAL";
+
+    case STATUS_BROKEN:
+        return "BROKEN";
+
+    case STATUS_ZOMBIE:
+        return "ZOMBIE";
+
+    default:
+        return "UNKNOWN";
+    }
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
+        const EventEntry* eventEntry) const {
+    for (DispatchEntry* dispatchEntry = outboundQueue.tailSentinel.prev;
+            dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) {
+        if (dispatchEntry->eventEntry == eventEntry) {
+            return dispatchEntry;
+        }
+    }
+    return NULL;
+}
+
+
+// --- InputDispatcher::CommandEntry ---
+
+InputDispatcher::CommandEntry::CommandEntry() :
+    keyEntry(NULL) {
+}
+
+InputDispatcher::CommandEntry::~CommandEntry() {
+}
+
+
+// --- InputDispatcher::TouchState ---
+
+InputDispatcher::TouchState::TouchState() :
+    down(false), split(false) {
+}
+
+InputDispatcher::TouchState::~TouchState() {
+}
+
+void InputDispatcher::TouchState::reset() {
+    down = false;
+    split = false;
+    windows.clear();
+}
+
+void InputDispatcher::TouchState::copyFrom(const TouchState& other) {
+    down = other.down;
+    split = other.split;
+    windows.clear();
+    windows.appendVector(other.windows);
+}
+
+void InputDispatcher::TouchState::addOrUpdateWindow(const InputWindow* window,
+        int32_t targetFlags, BitSet32 pointerIds) {
+    if (targetFlags & InputTarget::FLAG_SPLIT) {
+        split = true;
+    }
+
+    for (size_t i = 0; i < windows.size(); i++) {
+        TouchedWindow& touchedWindow = windows.editItemAt(i);
+        if (touchedWindow.window == window) {
+            touchedWindow.targetFlags |= targetFlags;
+            touchedWindow.pointerIds.value |= pointerIds.value;
+            return;
+        }
+    }
+
+    windows.push();
+
+    TouchedWindow& touchedWindow = windows.editTop();
+    touchedWindow.window = window;
+    touchedWindow.targetFlags = targetFlags;
+    touchedWindow.pointerIds = pointerIds;
+    touchedWindow.channel = window->inputChannel;
+}
+
+void InputDispatcher::TouchState::removeOutsideTouchWindows() {
+    for (size_t i = 0 ; i < windows.size(); ) {
+        if (windows[i].targetFlags & InputTarget::FLAG_OUTSIDE) {
+            windows.removeAt(i);
+        } else {
+            i += 1;
+        }
+    }
+}
+
+const InputWindow* InputDispatcher::TouchState::getFirstForegroundWindow() {
+    for (size_t i = 0; i < windows.size(); i++) {
+        if (windows[i].targetFlags & InputTarget::FLAG_FOREGROUND) {
+            return windows[i].window;
+        }
+    }
+    return NULL;
+}
+
+
+// --- InputDispatcherThread ---
+
+InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
+        Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
+}
+
+InputDispatcherThread::~InputDispatcherThread() {
+}
+
+bool InputDispatcherThread::threadLoop() {
+    mDispatcher->dispatchOnce();
+    return true;
+}
+
+} // namespace android
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
new file mode 100644
index 0000000..7305601
--- /dev/null
+++ b/services/input/InputDispatcher.h
@@ -0,0 +1,1110 @@
+/*
+ * 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_DISPATCHER_H
+#define _UI_INPUT_DISPATCHER_H
+
+#include <ui/Input.h>
+#include <ui/InputTransport.h>
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Looper.h>
+#include <utils/Pool.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <limits.h>
+
+
+namespace android {
+
+/*
+ * Constants used to report the outcome of input event injection.
+ */
+enum {
+    /* (INTERNAL USE ONLY) Specifies that injection is pending and its outcome is unknown. */
+    INPUT_EVENT_INJECTION_PENDING = -1,
+
+    /* Injection succeeded. */
+    INPUT_EVENT_INJECTION_SUCCEEDED = 0,
+
+    /* Injection failed because the injector did not have permission to inject
+     * into the application with input focus. */
+    INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1,
+
+    /* Injection failed because there were no available input targets. */
+    INPUT_EVENT_INJECTION_FAILED = 2,
+
+    /* Injection failed due to a timeout. */
+    INPUT_EVENT_INJECTION_TIMED_OUT = 3
+};
+
+/*
+ * Constants used to determine the input event injection synchronization mode.
+ */
+enum {
+    /* Injection is asynchronous and is assumed always to be successful. */
+    INPUT_EVENT_INJECTION_SYNC_NONE = 0,
+
+    /* Waits for previous events to be dispatched so that the input dispatcher can determine
+     * whether input event injection willbe permitted based on the current input focus.
+     * Does not wait for the input event to finish processing. */
+    INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1,
+
+    /* Waits for the input event to be completely processed. */
+    INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2,
+};
+
+
+/*
+ * An input target specifies how an input event is to be dispatched to a particular window
+ * including the window's input channel, control flags, a timeout, and an X / Y offset to
+ * be added to input event coordinates to compensate for the absolute position of the
+ * window area.
+ */
+struct InputTarget {
+    enum {
+        /* This flag indicates that the event is being delivered to a foreground application. */
+        FLAG_FOREGROUND = 0x01,
+
+        /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+         * of the area of this target and so should instead be delivered as an
+         * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
+        FLAG_OUTSIDE = 0x02,
+
+        /* This flag indicates that the target of a MotionEvent is partly or wholly
+         * obscured by another visible window above it.  The motion event should be
+         * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+        FLAG_WINDOW_IS_OBSCURED = 0x04,
+
+        /* This flag indicates that a motion event is being split across multiple windows. */
+        FLAG_SPLIT = 0x08,
+    };
+
+    // The input channel to be targeted.
+    sp<InputChannel> inputChannel;
+
+    // Flags for the input target.
+    int32_t flags;
+
+    // The x and y offset to add to a MotionEvent as it is delivered.
+    // (ignored for KeyEvents)
+    float xOffset, yOffset;
+
+    // The subset of pointer ids to include in motion events dispatched to this input target
+    // if FLAG_SPLIT is set.
+    BitSet32 pointerIds;
+};
+
+
+/*
+ * An input window describes the bounds of a window that can receive input.
+ */
+struct InputWindow {
+    // Window flags from WindowManager.LayoutParams
+    enum {
+        FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001,
+        FLAG_DIM_BEHIND        = 0x00000002,
+        FLAG_BLUR_BEHIND        = 0x00000004,
+        FLAG_NOT_FOCUSABLE      = 0x00000008,
+        FLAG_NOT_TOUCHABLE      = 0x00000010,
+        FLAG_NOT_TOUCH_MODAL    = 0x00000020,
+        FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
+        FLAG_KEEP_SCREEN_ON     = 0x00000080,
+        FLAG_LAYOUT_IN_SCREEN   = 0x00000100,
+        FLAG_LAYOUT_NO_LIMITS   = 0x00000200,
+        FLAG_FULLSCREEN      = 0x00000400,
+        FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800,
+        FLAG_DITHER             = 0x00001000,
+        FLAG_SECURE             = 0x00002000,
+        FLAG_SCALED             = 0x00004000,
+        FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000,
+        FLAG_LAYOUT_INSET_DECOR = 0x00010000,
+        FLAG_ALT_FOCUSABLE_IM = 0x00020000,
+        FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
+        FLAG_SHOW_WHEN_LOCKED = 0x00080000,
+        FLAG_SHOW_WALLPAPER = 0x00100000,
+        FLAG_TURN_SCREEN_ON = 0x00200000,
+        FLAG_DISMISS_KEYGUARD = 0x00400000,
+        FLAG_SPLIT_TOUCH = 0x00800000,
+        FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
+        FLAG_COMPATIBLE_WINDOW = 0x20000000,
+        FLAG_SYSTEM_ERROR = 0x40000000,
+    };
+
+    // Window types from WindowManager.LayoutParams
+    enum {
+        FIRST_APPLICATION_WINDOW = 1,
+        TYPE_BASE_APPLICATION   = 1,
+        TYPE_APPLICATION        = 2,
+        TYPE_APPLICATION_STARTING = 3,
+        LAST_APPLICATION_WINDOW = 99,
+        FIRST_SUB_WINDOW        = 1000,
+        TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW,
+        TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1,
+        TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
+        TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
+        TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4,
+        LAST_SUB_WINDOW         = 1999,
+        FIRST_SYSTEM_WINDOW     = 2000,
+        TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW,
+        TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1,
+        TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2,
+        TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3,
+        TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4,
+        TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5,
+        TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6,
+        TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7,
+        TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8,
+        TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9,
+        TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10,
+        TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11,
+        TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
+        TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13,
+        TYPE_STATUS_BAR_SUB_PANEL  = FIRST_SYSTEM_WINDOW+14,
+        TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15,
+        TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16,
+        TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+17,
+        LAST_SYSTEM_WINDOW      = 2999,
+    };
+
+    sp<InputChannel> inputChannel;
+    String8 name;
+    int32_t layoutParamsFlags;
+    int32_t layoutParamsType;
+    nsecs_t dispatchingTimeout;
+    int32_t frameLeft;
+    int32_t frameTop;
+    int32_t frameRight;
+    int32_t frameBottom;
+    int32_t visibleFrameLeft;
+    int32_t visibleFrameTop;
+    int32_t visibleFrameRight;
+    int32_t visibleFrameBottom;
+    int32_t touchableAreaLeft;
+    int32_t touchableAreaTop;
+    int32_t touchableAreaRight;
+    int32_t touchableAreaBottom;
+    bool visible;
+    bool canReceiveKeys;
+    bool hasFocus;
+    bool hasWallpaper;
+    bool paused;
+    int32_t layer;
+    int32_t ownerPid;
+    int32_t ownerUid;
+
+    bool touchableAreaContainsPoint(int32_t x, int32_t y) const;
+    bool frameContainsPoint(int32_t x, int32_t y) const;
+
+    /* Returns true if the window is of a trusted type that is allowed to silently
+     * overlay other windows for the purpose of implementing the secure views feature.
+     * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+     * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+     */
+    bool isTrustedOverlay() const;
+
+    bool supportsSplitTouch() const;
+};
+
+
+/*
+ * A private handle type used by the input manager to track the window.
+ */
+class InputApplicationHandle : public RefBase {
+protected:
+    InputApplicationHandle() { }
+    virtual ~InputApplicationHandle() { }
+};
+
+
+/*
+ * An input application describes properties of an application that can receive input.
+ */
+struct InputApplication {
+    String8 name;
+    nsecs_t dispatchingTimeout;
+    sp<InputApplicationHandle> handle;
+};
+
+
+/*
+ * Input dispatcher policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI.  This interface is also mocked in the unit tests.
+ */
+class InputDispatcherPolicyInterface : public virtual RefBase {
+protected:
+    InputDispatcherPolicyInterface() { }
+    virtual ~InputDispatcherPolicyInterface() { }
+
+public:
+    /* Notifies the system that a configuration change has occurred. */
+    virtual void notifyConfigurationChanged(nsecs_t when) = 0;
+
+    /* Notifies the system that an application is not responding.
+     * Returns a new timeout to continue waiting, or 0 to abort dispatch. */
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputChannel>& inputChannel) = 0;
+
+    /* Notifies the system that an input channel is unrecoverably broken. */
+    virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) = 0;
+
+    /* Gets the key repeat initial timeout or -1 if automatic key repeating is disabled. */
+    virtual nsecs_t getKeyRepeatTimeout() = 0;
+
+    /* Gets the key repeat inter-key delay. */
+    virtual nsecs_t getKeyRepeatDelay() = 0;
+
+    /* Gets the maximum suggested event delivery rate per second.
+     * This value is used to throttle motion event movement actions on a per-device
+     * basis.  It is not intended to be a hard limit.
+     */
+    virtual int32_t getMaxEventsPerSecond() = 0;
+
+    /* Intercepts a key event immediately before queueing it.
+     * The policy can use this method as an opportunity to perform power management functions
+     * and early event preprocessing such as updating policy flags.
+     *
+     * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+     * should be dispatched to applications.
+     */
+    virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0;
+
+    /* Intercepts a generic touch, trackball or other event before queueing it.
+     * The policy can use this method as an opportunity to perform power management functions
+     * and early event preprocessing such as updating policy flags.
+     *
+     * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+     * should be dispatched to applications.
+     */
+    virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0;
+
+    /* Allows the policy a chance to intercept a key before dispatching. */
+    virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
+    /* Allows the policy a chance to perform default processing for an unhandled key.
+     * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */
+    virtual bool dispatchUnhandledKey(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0;
+
+    /* Notifies the policy about switch events.
+     */
+    virtual void notifySwitch(nsecs_t when,
+            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;
+
+    /* Poke user activity for an event dispatched to a window. */
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
+
+    /* Checks whether a given application pid/uid has permission to inject input events
+     * into other applications.
+     *
+     * This method is special in that its implementation promises to be non-reentrant and
+     * is safe to call while holding other locks.  (Most other methods make no such guarantees!)
+     */
+    virtual bool checkInjectEventsPermissionNonReentrant(
+            int32_t injectorPid, int32_t injectorUid) = 0;
+};
+
+
+/* Notifies the system about input events generated by the input reader.
+ * The dispatcher is expected to be mostly asynchronous. */
+class InputDispatcherInterface : public virtual RefBase {
+protected:
+    InputDispatcherInterface() { }
+    virtual ~InputDispatcherInterface() { }
+
+public:
+    /* Dumps the state of the input dispatcher.
+     *
+     * This method may be called on any thread (usually by the input manager). */
+    virtual void dump(String8& dump) = 0;
+
+    /* Runs a single iteration of the dispatch loop.
+     * Nominally processes one queued event, a timeout, or a response from an input consumer.
+     *
+     * This method should only be called on the input dispatcher thread.
+     */
+    virtual void dispatchOnce() = 0;
+
+    /* Notifies the dispatcher about new events.
+     *
+     * These methods should only be called on the input reader thread.
+     */
+    virtual void notifyConfigurationChanged(nsecs_t eventTime) = 0;
+    virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+            int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
+    virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
+            uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+            float xPrecision, float yPrecision, nsecs_t downTime) = 0;
+    virtual void notifySwitch(nsecs_t when,
+            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;
+
+    /* Injects an input event and optionally waits for sync.
+     * The synchronization mode determines whether the method blocks while waiting for
+     * input injection to proceed.
+     * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual int32_t injectInputEvent(const InputEvent* event,
+            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
+
+    /* Sets the list of input windows.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows) = 0;
+
+    /* Sets the focused application.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void setFocusedApplication(const InputApplication* inputApplication) = 0;
+
+    /* Sets the input dispatching mode.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
+
+    /* Transfers touch focus from the window associated with one channel to the
+     * window associated with the other channel.
+     *
+     * Returns true on success.  False if the window did not actually have touch focus.
+     */
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel) = 0;
+
+    /* Registers or unregister input channels that may be used as targets for input events.
+     * If monitor is true, the channel will receive a copy of all input events.
+     *
+     * These methods may be called on any thread (usually by the input manager).
+     */
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) = 0;
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
+};
+
+/* Dispatches events to input targets.  Some functions of the input dispatcher, such as
+ * identifying input targets, are controlled by a separate policy object.
+ *
+ * IMPORTANT INVARIANT:
+ *     Because the policy can potentially block or cause re-entrance into the input dispatcher,
+ *     the input dispatcher never calls into the policy while holding its internal locks.
+ *     The implementation is also carefully designed to recover from scenarios such as an
+ *     input channel becoming unregistered while identifying input targets or processing timeouts.
+ *
+ *     Methods marked 'Locked' must be called with the lock acquired.
+ *
+ *     Methods marked 'LockedInterruptible' must be called with the lock acquired but
+ *     may during the course of their execution release the lock, call into the policy, and
+ *     then reacquire the lock.  The caller is responsible for recovering gracefully.
+ *
+ *     A 'LockedInterruptible' method may called a 'Locked' method, but NOT vice-versa.
+ */
+class InputDispatcher : public InputDispatcherInterface {
+protected:
+    virtual ~InputDispatcher();
+
+public:
+    explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
+
+    virtual void dump(String8& dump);
+
+    virtual void dispatchOnce();
+
+    virtual void notifyConfigurationChanged(nsecs_t eventTime);
+    virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+            int32_t scanCode, int32_t metaState, nsecs_t downTime);
+    virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
+            uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+            float xPrecision, float yPrecision, nsecs_t downTime);
+    virtual void notifySwitch(nsecs_t when,
+            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ;
+
+    virtual int32_t injectInputEvent(const InputEvent* event,
+            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
+
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows);
+    virtual void setFocusedApplication(const InputApplication* inputApplication);
+    virtual void setInputDispatchMode(bool enabled, bool frozen);
+
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel);
+
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor);
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+private:
+    template <typename T>
+    struct Link {
+        T* next;
+        T* prev;
+    };
+
+    struct InjectionState {
+        mutable int32_t refCount;
+
+        int32_t injectorPid;
+        int32_t injectorUid;
+        int32_t injectionResult;  // initially INPUT_EVENT_INJECTION_PENDING
+        bool injectionIsAsync; // set to true if injection is not waiting for the result
+        int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
+    };
+
+    struct EventEntry : Link<EventEntry> {
+        enum {
+            TYPE_SENTINEL,
+            TYPE_CONFIGURATION_CHANGED,
+            TYPE_KEY,
+            TYPE_MOTION
+        };
+
+        mutable int32_t refCount;
+        int32_t type;
+        nsecs_t eventTime;
+        uint32_t policyFlags;
+        InjectionState* injectionState;
+
+        bool dispatchInProgress; // initially false, set to true while dispatching
+
+        inline bool isInjected() { return injectionState != NULL; }
+    };
+
+    struct ConfigurationChangedEntry : EventEntry {
+    };
+
+    struct KeyEntry : EventEntry {
+        int32_t deviceId;
+        int32_t source;
+        int32_t action;
+        int32_t flags;
+        int32_t keyCode;
+        int32_t scanCode;
+        int32_t metaState;
+        int32_t repeatCount;
+        nsecs_t downTime;
+
+        bool syntheticRepeat; // set to true for synthetic key repeats
+
+        enum InterceptKeyResult {
+            INTERCEPT_KEY_RESULT_UNKNOWN,
+            INTERCEPT_KEY_RESULT_SKIP,
+            INTERCEPT_KEY_RESULT_CONTINUE,
+        };
+        InterceptKeyResult interceptKeyResult; // set based on the interception result
+    };
+
+    struct MotionSample {
+        MotionSample* next;
+
+        nsecs_t eventTime;
+        PointerCoords pointerCoords[MAX_POINTERS];
+    };
+
+    struct MotionEntry : EventEntry {
+        int32_t deviceId;
+        int32_t source;
+        int32_t action;
+        int32_t flags;
+        int32_t metaState;
+        int32_t edgeFlags;
+        float xPrecision;
+        float yPrecision;
+        nsecs_t downTime;
+        uint32_t pointerCount;
+        int32_t pointerIds[MAX_POINTERS];
+
+        // Linked list of motion samples associated with this motion event.
+        MotionSample firstSample;
+        MotionSample* lastSample;
+
+        uint32_t countSamples() const;
+    };
+
+    // Tracks the progress of dispatching a particular event to a particular connection.
+    struct DispatchEntry : Link<DispatchEntry> {
+        EventEntry* eventEntry; // the event to dispatch
+        int32_t targetFlags;
+        float xOffset;
+        float yOffset;
+
+        // True if dispatch has started.
+        bool inProgress;
+
+        // For motion events:
+        //   Pointer to the first motion sample to dispatch in this cycle.
+        //   Usually NULL to indicate that the list of motion samples begins at
+        //   MotionEntry::firstSample.  Otherwise, some samples were dispatched in a previous
+        //   cycle and this pointer indicates the location of the first remainining sample
+        //   to dispatch during the current cycle.
+        MotionSample* headMotionSample;
+        //   Pointer to a motion sample to dispatch in the next cycle if the dispatcher was
+        //   unable to send all motion samples during this cycle.  On the next cycle,
+        //   headMotionSample will be initialized to tailMotionSample and tailMotionSample
+        //   will be set to NULL.
+        MotionSample* tailMotionSample;
+
+        inline bool hasForegroundTarget() const {
+            return targetFlags & InputTarget::FLAG_FOREGROUND;
+        }
+
+        inline bool isSplit() const {
+            return targetFlags & InputTarget::FLAG_SPLIT;
+        }
+    };
+
+    // A command entry captures state and behavior for an action to be performed in the
+    // dispatch loop after the initial processing has taken place.  It is essentially
+    // a kind of continuation used to postpone sensitive policy interactions to a point
+    // in the dispatch loop where it is safe to release the lock (generally after finishing
+    // the critical parts of the dispatch cycle).
+    //
+    // The special thing about commands is that they can voluntarily release and reacquire
+    // the dispatcher lock at will.  Initially when the command starts running, the
+    // dispatcher lock is held.  However, if the command needs to call into the policy to
+    // do some work, it can release the lock, do the work, then reacquire the lock again
+    // before returning.
+    //
+    // This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch
+    // never calls into the policy while holding its lock.
+    //
+    // Commands are implicitly 'LockedInterruptible'.
+    struct CommandEntry;
+    typedef void (InputDispatcher::*Command)(CommandEntry* commandEntry);
+
+    class Connection;
+    struct CommandEntry : Link<CommandEntry> {
+        CommandEntry();
+        ~CommandEntry();
+
+        Command command;
+
+        // parameters for the command (usage varies by command)
+        sp<Connection> connection;
+        nsecs_t eventTime;
+        KeyEntry* keyEntry;
+        sp<InputChannel> inputChannel;
+        sp<InputApplicationHandle> inputApplicationHandle;
+        int32_t userActivityEventType;
+        bool handled;
+    };
+
+    // Generic queue implementation.
+    template <typename T>
+    struct Queue {
+        T headSentinel;
+        T tailSentinel;
+
+        inline Queue() {
+            headSentinel.prev = NULL;
+            headSentinel.next = & tailSentinel;
+            tailSentinel.prev = & headSentinel;
+            tailSentinel.next = NULL;
+        }
+
+        inline bool isEmpty() const {
+            return headSentinel.next == & tailSentinel;
+        }
+
+        inline void enqueueAtTail(T* entry) {
+            T* last = tailSentinel.prev;
+            last->next = entry;
+            entry->prev = last;
+            entry->next = & tailSentinel;
+            tailSentinel.prev = entry;
+        }
+
+        inline void enqueueAtHead(T* entry) {
+            T* first = headSentinel.next;
+            headSentinel.next = entry;
+            entry->prev = & headSentinel;
+            entry->next = first;
+            first->prev = entry;
+        }
+
+        inline void dequeue(T* entry) {
+            entry->prev->next = entry->next;
+            entry->next->prev = entry->prev;
+        }
+
+        inline T* dequeueAtHead() {
+            T* first = headSentinel.next;
+            dequeue(first);
+            return first;
+        }
+
+        uint32_t count() const;
+    };
+
+    /* Allocates queue entries and performs reference counting as needed. */
+    class Allocator {
+    public:
+        Allocator();
+
+        InjectionState* obtainInjectionState(int32_t injectorPid, int32_t injectorUid);
+        ConfigurationChangedEntry* obtainConfigurationChangedEntry(nsecs_t eventTime);
+        KeyEntry* obtainKeyEntry(nsecs_t eventTime,
+                int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
+                int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+                int32_t repeatCount, nsecs_t downTime);
+        MotionEntry* obtainMotionEntry(nsecs_t eventTime,
+                int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
+                int32_t flags, int32_t metaState, int32_t edgeFlags,
+                float xPrecision, float yPrecision,
+                nsecs_t downTime, uint32_t pointerCount,
+                const int32_t* pointerIds, const PointerCoords* pointerCoords);
+        DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry,
+                int32_t targetFlags, float xOffset, float yOffset);
+        CommandEntry* obtainCommandEntry(Command command);
+
+        void releaseInjectionState(InjectionState* injectionState);
+        void releaseEventEntry(EventEntry* entry);
+        void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry);
+        void releaseKeyEntry(KeyEntry* entry);
+        void releaseMotionEntry(MotionEntry* entry);
+        void releaseDispatchEntry(DispatchEntry* entry);
+        void releaseCommandEntry(CommandEntry* entry);
+
+        void recycleKeyEntry(KeyEntry* entry);
+
+        void appendMotionSample(MotionEntry* motionEntry,
+                nsecs_t eventTime, const PointerCoords* pointerCoords);
+
+    private:
+        Pool<InjectionState> mInjectionStatePool;
+        Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool;
+        Pool<KeyEntry> mKeyEntryPool;
+        Pool<MotionEntry> mMotionEntryPool;
+        Pool<MotionSample> mMotionSamplePool;
+        Pool<DispatchEntry> mDispatchEntryPool;
+        Pool<CommandEntry> mCommandEntryPool;
+
+        void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime,
+                uint32_t policyFlags);
+        void releaseEventEntryInjectionState(EventEntry* entry);
+    };
+
+    /* Tracks dispatched key and motion event state so that cancelation events can be
+     * synthesized when events are dropped. */
+    class InputState {
+    public:
+        // Specifies whether a given event will violate input state consistency.
+        enum Consistency {
+            // The event is consistent with the current input state.
+            CONSISTENT,
+            // The event is inconsistent with the current input state but applications
+            // will tolerate it.  eg. Down followed by another down.
+            TOLERABLE,
+            // The event is inconsistent with the current input state and will probably
+            // cause applications to crash.  eg. Up without prior down, move with
+            // unexpected number of pointers.
+            BROKEN
+        };
+
+        // Specifies the sources to cancel.
+        enum CancelationOptions {
+            CANCEL_ALL_EVENTS = 0,
+            CANCEL_POINTER_EVENTS = 1,
+            CANCEL_NON_POINTER_EVENTS = 2,
+            CANCEL_FALLBACK_EVENTS = 3,
+        };
+
+        InputState();
+        ~InputState();
+
+        // Returns true if there is no state to be canceled.
+        bool isNeutral() const;
+
+        // Records tracking information for an event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackEvent(const EventEntry* entry);
+
+        // Records tracking information for a key event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackKey(const KeyEntry* entry);
+
+        // Records tracking information for a motion event that has just been published.
+        // Returns whether the event is consistent with the current input state.
+        Consistency trackMotion(const MotionEntry* entry);
+
+        // Synthesizes cancelation events for the current state and resets the tracked state.
+        void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
+                Vector<EventEntry*>& outEvents, CancelationOptions options);
+
+        // Clears the current state.
+        void clear();
+
+        // Copies pointer-related parts of the input state to another instance.
+        void copyPointerStateTo(InputState& other) const;
+
+    private:
+        struct KeyMemento {
+            int32_t deviceId;
+            int32_t source;
+            int32_t keyCode;
+            int32_t scanCode;
+            int32_t flags;
+            nsecs_t downTime;
+        };
+
+        struct MotionMemento {
+            int32_t deviceId;
+            int32_t source;
+            float xPrecision;
+            float yPrecision;
+            nsecs_t downTime;
+            uint32_t pointerCount;
+            int32_t pointerIds[MAX_POINTERS];
+            PointerCoords pointerCoords[MAX_POINTERS];
+
+            void setPointers(const MotionEntry* entry);
+        };
+
+        Vector<KeyMemento> mKeyMementos;
+        Vector<MotionMemento> mMotionMementos;
+
+        static bool shouldCancelKey(const KeyMemento& memento,
+                CancelationOptions options);
+        static bool shouldCancelMotion(const MotionMemento& memento,
+                CancelationOptions options);
+    };
+
+    /* Manages the dispatch state associated with a single input channel. */
+    class Connection : public RefBase {
+    protected:
+        virtual ~Connection();
+
+    public:
+        enum Status {
+            // Everything is peachy.
+            STATUS_NORMAL,
+            // An unrecoverable communication error has occurred.
+            STATUS_BROKEN,
+            // The input channel has been unregistered.
+            STATUS_ZOMBIE
+        };
+
+        Status status;
+        sp<InputChannel> inputChannel;
+        InputPublisher inputPublisher;
+        InputState inputState;
+        Queue<DispatchEntry> outboundQueue;
+
+        nsecs_t lastEventTime; // the time when the event was originally captured
+        nsecs_t lastDispatchTime; // the time when the last event was dispatched
+
+        explicit Connection(const sp<InputChannel>& inputChannel);
+
+        inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
+
+        const char* getStatusLabel() const;
+
+        // Finds a DispatchEntry in the outbound queue associated with the specified event.
+        // Returns NULL if not found.
+        DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const;
+
+        // Gets the time since the current event was originally obtained from the input driver.
+        inline double getEventLatencyMillis(nsecs_t currentTime) const {
+            return (currentTime - lastEventTime) / 1000000.0;
+        }
+
+        // Gets the time since the current event entered the outbound dispatch queue.
+        inline double getDispatchLatencyMillis(nsecs_t currentTime) const {
+            return (currentTime - lastDispatchTime) / 1000000.0;
+        }
+
+        status_t initialize();
+    };
+
+    enum DropReason {
+        DROP_REASON_NOT_DROPPED = 0,
+        DROP_REASON_POLICY = 1,
+        DROP_REASON_APP_SWITCH = 2,
+        DROP_REASON_DISABLED = 3,
+    };
+
+    sp<InputDispatcherPolicyInterface> mPolicy;
+
+    Mutex mLock;
+
+    Allocator mAllocator;
+    sp<Looper> mLooper;
+
+    EventEntry* mPendingEvent;
+    Queue<EventEntry> mInboundQueue;
+    Queue<CommandEntry> mCommandQueue;
+
+    Vector<EventEntry*> mTempCancelationEvents;
+
+    void dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay,
+            nsecs_t* nextWakeupTime);
+
+    // Enqueues an inbound event.  Returns true if mLooper->wake() should be called.
+    bool enqueueInboundEventLocked(EventEntry* entry);
+
+    // Cleans up input state when dropping an inbound event.
+    void dropInboundEventLocked(EventEntry* entry, DropReason dropReason);
+
+    // App switch latency optimization.
+    bool mAppSwitchSawKeyDown;
+    nsecs_t mAppSwitchDueTime;
+
+    static bool isAppSwitchKeyCode(int32_t keyCode);
+    bool isAppSwitchKeyEventLocked(KeyEntry* keyEntry);
+    bool isAppSwitchPendingLocked();
+    void resetPendingAppSwitchLocked(bool handled);
+
+    // All registered connections mapped by receive pipe file descriptor.
+    KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd;
+
+    ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel);
+
+    // Active connections are connections that have a non-empty outbound queue.
+    // We don't use a ref-counted pointer here because we explicitly abort connections
+    // during unregistration which causes the connection's outbound queue to be cleared
+    // and the connection itself to be deactivated.
+    Vector<Connection*> mActiveConnections;
+
+    // Input channels that will receive a copy of all input events.
+    Vector<sp<InputChannel> > mMonitoringChannels;
+
+    // Event injection and synchronization.
+    Condition mInjectionResultAvailableCondition;
+    bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
+    void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
+
+    Condition mInjectionSyncFinishedCondition;
+    void incrementPendingForegroundDispatchesLocked(EventEntry* entry);
+    void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
+
+    // Throttling state.
+    struct ThrottleState {
+        nsecs_t minTimeBetweenEvents;
+
+        nsecs_t lastEventTime;
+        int32_t lastDeviceId;
+        uint32_t lastSource;
+
+        uint32_t originalSampleCount; // only collected during debugging
+    } mThrottleState;
+
+    // Key repeat tracking.
+    struct KeyRepeatState {
+        KeyEntry* lastKeyEntry; // or null if no repeat
+        nsecs_t nextRepeatTime;
+    } mKeyRepeatState;
+
+    void resetKeyRepeatLocked();
+    KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime, nsecs_t keyRepeatTimeout);
+
+    // Deferred command processing.
+    bool runCommandsLockedInterruptible();
+    CommandEntry* postCommandLocked(Command command);
+
+    // Inbound event processing.
+    void drainInboundQueueLocked();
+    void releasePendingEventLocked();
+    void releaseInboundEventLocked(EventEntry* entry);
+
+    // Dispatch state.
+    bool mDispatchEnabled;
+    bool mDispatchFrozen;
+
+    Vector<InputWindow> mWindows;
+
+    const InputWindow* getWindowLocked(const sp<InputChannel>& inputChannel);
+
+    // Focus tracking for keys, trackball, etc.
+    const InputWindow* mFocusedWindow;
+
+    // Focus tracking for touch.
+    struct TouchedWindow {
+        const InputWindow* window;
+        int32_t targetFlags;
+        BitSet32 pointerIds;        // zero unless target flag FLAG_SPLIT is set
+        sp<InputChannel> channel;
+    };
+    struct TouchState {
+        bool down;
+        bool split;
+        Vector<TouchedWindow> windows;
+
+        TouchState();
+        ~TouchState();
+        void reset();
+        void copyFrom(const TouchState& other);
+        void addOrUpdateWindow(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds);
+        void removeOutsideTouchWindows();
+        const InputWindow* getFirstForegroundWindow();
+    };
+
+    TouchState mTouchState;
+    TouchState mTempTouchState;
+
+    // Focused application.
+    InputApplication* mFocusedApplication;
+    InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication
+    void releaseFocusedApplicationLocked();
+
+    // Dispatch inbound events.
+    bool dispatchConfigurationChangedLocked(
+            nsecs_t currentTime, ConfigurationChangedEntry* entry);
+    bool dispatchKeyLocked(
+            nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+            DropReason* dropReason, nsecs_t* nextWakeupTime);
+    bool dispatchMotionLocked(
+            nsecs_t currentTime, MotionEntry* entry,
+            DropReason* dropReason, nsecs_t* nextWakeupTime);
+    void dispatchEventToCurrentInputTargetsLocked(
+            nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample);
+
+    void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry);
+    void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
+
+    // The input targets that were most recently identified for dispatch.
+    bool mCurrentInputTargetsValid; // false while targets are being recomputed
+    Vector<InputTarget> mCurrentInputTargets;
+
+    enum InputTargetWaitCause {
+        INPUT_TARGET_WAIT_CAUSE_NONE,
+        INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
+        INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
+    };
+
+    InputTargetWaitCause mInputTargetWaitCause;
+    nsecs_t mInputTargetWaitStartTime;
+    nsecs_t mInputTargetWaitTimeoutTime;
+    bool mInputTargetWaitTimeoutExpired;
+
+    // Finding targets for input events.
+    void resetTargetsLocked();
+    void commitTargetsLocked();
+    int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
+            const InputApplication* application, const InputWindow* window,
+            nsecs_t* nextWakeupTime);
+    void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+            const sp<InputChannel>& inputChannel);
+    nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
+    void resetANRTimeoutsLocked();
+
+    int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
+            nsecs_t* nextWakeupTime);
+    int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
+            nsecs_t* nextWakeupTime);
+
+    void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
+            BitSet32 pointerIds);
+    void addMonitoringTargetsLocked();
+    void pokeUserActivityLocked(const EventEntry* eventEntry);
+    bool checkInjectionPermission(const InputWindow* window, const InjectionState* injectionState);
+    bool isWindowObscuredAtPointLocked(const InputWindow* window, int32_t x, int32_t y) const;
+    bool isWindowFinishedWithPreviousInputLocked(const InputWindow* window);
+    String8 getApplicationWindowLabelLocked(const InputApplication* application,
+            const InputWindow* window);
+
+    // Manage the dispatch cycle for a single connection.
+    // These methods are deliberately not Interruptible because doing all of the work
+    // with the mutex held makes it easier to ensure that connection invariants are maintained.
+    // If needed, the methods post commands to run later once the critical bits are done.
+    void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+            EventEntry* eventEntry, const InputTarget* inputTarget,
+            bool resumeWithAppendedMotionSample);
+    void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+    void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+            bool handled);
+    void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+    void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+    void drainOutboundQueueLocked(Connection* connection);
+    static int handleReceiveCallback(int receiveFd, int events, void* data);
+
+    void synthesizeCancelationEventsForAllConnectionsLocked(
+            InputState::CancelationOptions options, const char* reason);
+    void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel,
+            InputState::CancelationOptions options, const char* reason);
+    void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
+            InputState::CancelationOptions options, const char* reason);
+
+    // Splitting motion events across windows.
+    MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds);
+
+    // Reset and drop everything the dispatcher is doing.
+    void resetAndDropEverythingLocked(const char* reason);
+
+    // Dump state.
+    void dumpDispatchStateLocked(String8& dump);
+    void logDispatchStateLocked();
+
+    // Add or remove a connection to the mActiveConnections vector.
+    void activateConnectionLocked(Connection* connection);
+    void deactivateConnectionLocked(Connection* connection);
+
+    // Interesting events that we might like to log or tell the framework about.
+    void onDispatchCycleStartedLocked(
+            nsecs_t currentTime, const sp<Connection>& connection);
+    void onDispatchCycleFinishedLocked(
+            nsecs_t currentTime, const sp<Connection>& connection, bool handled);
+    void onDispatchCycleBrokenLocked(
+            nsecs_t currentTime, const sp<Connection>& connection);
+    void onANRLocked(
+            nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+            nsecs_t eventTime, nsecs_t waitStartTime);
+
+    // Outbound policy interactions.
+    void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
+    void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
+    void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
+    void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+    void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry);
+    void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+    void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry);
+
+    // Statistics gathering.
+    void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+            int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
+};
+
+/* Enqueues and dispatches input events, endlessly. */
+class InputDispatcherThread : public Thread {
+public:
+    explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
+    ~InputDispatcherThread();
+
+private:
+    virtual bool threadLoop();
+
+    sp<InputDispatcherInterface> mDispatcher;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_DISPATCHER_H
diff --git a/services/input/InputManager.cpp b/services/input/InputManager.cpp
new file mode 100644
index 0000000..5dfa5d5
--- /dev/null
+++ b/services/input/InputManager.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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 "InputManager"
+
+//#define LOG_NDEBUG 0
+
+#include "InputManager.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+InputManager::InputManager(
+        const sp<EventHubInterface>& eventHub,
+        const sp<InputReaderPolicyInterface>& readerPolicy,
+        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
+    mDispatcher = new InputDispatcher(dispatcherPolicy);
+    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
+    initialize();
+}
+
+InputManager::InputManager(
+        const sp<InputReaderInterface>& reader,
+        const sp<InputDispatcherInterface>& dispatcher) :
+        mReader(reader),
+        mDispatcher(dispatcher) {
+    initialize();
+}
+
+InputManager::~InputManager() {
+    stop();
+}
+
+void InputManager::initialize() {
+    mReaderThread = new InputReaderThread(mReader);
+    mDispatcherThread = new InputDispatcherThread(mDispatcher);
+}
+
+status_t InputManager::start() {
+    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+    if (result) {
+        LOGE("Could not start InputDispatcher thread due to error %d.", result);
+        return result;
+    }
+
+    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+    if (result) {
+        LOGE("Could not start InputReader thread due to error %d.", result);
+
+        mDispatcherThread->requestExit();
+        return result;
+    }
+
+    return OK;
+}
+
+status_t InputManager::stop() {
+    status_t result = mReaderThread->requestExitAndWait();
+    if (result) {
+        LOGW("Could not stop InputReader thread due to error %d.", result);
+    }
+
+    result = mDispatcherThread->requestExitAndWait();
+    if (result) {
+        LOGW("Could not stop InputDispatcher thread due to error %d.", result);
+    }
+
+    return OK;
+}
+
+sp<InputReaderInterface> InputManager::getReader() {
+    return mReader;
+}
+
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+    return mDispatcher;
+}
+
+} // namespace android
diff --git a/services/input/InputManager.h b/services/input/InputManager.h
new file mode 100644
index 0000000..df4d299e
--- /dev/null
+++ b/services/input/InputManager.h
@@ -0,0 +1,109 @@
+/*
+ * 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_MANAGER_H
+#define _UI_INPUT_MANAGER_H
+
+/**
+ * Native input manager.
+ */
+
+#include "EventHub.h"
+#include "InputReader.h"
+#include "InputDispatcher.h"
+
+#include <ui/Input.h>
+#include <ui/InputTransport.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * The input manager is the core of the system event processing.
+ *
+ * The input manager uses two threads.
+ *
+ * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events,
+ *    applies policy, and posts messages to a queue managed by the DispatcherThread.
+ * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the
+ *    queue and asynchronously dispatches them to applications.
+ *
+ * By design, the InputReaderThread class and InputDispatcherThread class do not share any
+ * internal state.  Moreover, all communication is done one way from the InputReaderThread
+ * into the InputDispatcherThread and never the reverse.  Both classes may interact with the
+ * InputDispatchPolicy, however.
+ *
+ * The InputManager class never makes any calls into Java itself.  Instead, the
+ * InputDispatchPolicy is responsible for performing all external interactions with the
+ * system, including calling DVM services.
+ */
+class InputManagerInterface : public virtual RefBase {
+protected:
+    InputManagerInterface() { }
+    virtual ~InputManagerInterface() { }
+
+public:
+    /* Starts the input manager threads. */
+    virtual status_t start() = 0;
+
+    /* Stops the input manager threads and waits for them to exit. */
+    virtual status_t stop() = 0;
+
+    /* Gets the input reader. */
+    virtual sp<InputReaderInterface> getReader() = 0;
+
+    /* Gets the input dispatcher. */
+    virtual sp<InputDispatcherInterface> getDispatcher() = 0;
+};
+
+class InputManager : public InputManagerInterface {
+protected:
+    virtual ~InputManager();
+
+public:
+    InputManager(
+            const sp<EventHubInterface>& eventHub,
+            const sp<InputReaderPolicyInterface>& readerPolicy,
+            const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);
+
+    // (used for testing purposes)
+    InputManager(
+            const sp<InputReaderInterface>& reader,
+            const sp<InputDispatcherInterface>& dispatcher);
+
+    virtual status_t start();
+    virtual status_t stop();
+
+    virtual sp<InputReaderInterface> getReader();
+    virtual sp<InputDispatcherInterface> getDispatcher();
+
+private:
+    sp<InputReaderInterface> mReader;
+    sp<InputReaderThread> mReaderThread;
+
+    sp<InputDispatcherInterface> mDispatcher;
+    sp<InputDispatcherThread> mDispatcherThread;
+
+    void initialize();
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_MANAGER_H
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
new file mode 100644
index 0000000..7a45de6
--- /dev/null
+++ b/services/input/InputReader.cpp
@@ -0,0 +1,3620 @@
+/*
+ * 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
+
+#include "InputReader.h"
+
+#include <cutils/log.h>
+#include <ui/Keyboard.h>
+#include <ui/VirtualKeyMap.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+
+#define INDENT "  "
+#define INDENT2 "    "
+#define INDENT3 "      "
+#define INDENT4 "        "
+
+namespace android {
+
+// --- 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 pythag(float x, float y) {
+    return sqrtf(x * x + y * y);
+}
+
+static inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+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 },
+};
+static const int keyCodeRotationMapSize =
+        sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
+
+int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
+    if (orientation != DISPLAY_ORIENTATION_0) {
+        for (int i = 0; i < keyCodeRotationMapSize; i++) {
+            if (keyCode == keyCodeRotationMap[i][0]) {
+                return keyCodeRotationMap[i][orientation];
+            }
+        }
+    }
+    return keyCode;
+}
+
+static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) {
+    return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0;
+}
+
+
+// --- InputReader ---
+
+InputReader::InputReader(const sp<EventHubInterface>& eventHub,
+        const sp<InputReaderPolicyInterface>& policy,
+        const sp<InputDispatcherInterface>& dispatcher) :
+        mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
+        mGlobalMetaState(0) {
+    configureExcludedDevices();
+    updateGlobalMetaState();
+    updateInputConfiguration();
+}
+
+InputReader::~InputReader() {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        delete mDevices.valueAt(i);
+    }
+}
+
+void InputReader::loopOnce() {
+    RawEvent rawEvent;
+    mEventHub->getEvent(& rawEvent);
+
+#if DEBUG_RAW_EVENTS
+    LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d",
+            rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
+            rawEvent.value);
+#endif
+
+    process(& rawEvent);
+}
+
+void InputReader::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EventHubInterface::DEVICE_ADDED:
+        addDevice(rawEvent->deviceId);
+        break;
+
+    case EventHubInterface::DEVICE_REMOVED:
+        removeDevice(rawEvent->deviceId);
+        break;
+
+    case EventHubInterface::FINISHED_DEVICE_SCAN:
+        handleConfigurationChanged(rawEvent->when);
+        break;
+
+    default:
+        consumeEvent(rawEvent);
+        break;
+    }
+}
+
+void InputReader::addDevice(int32_t deviceId) {
+    String8 name = mEventHub->getDeviceName(deviceId);
+    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+
+    InputDevice* device = createDevice(deviceId, name, classes);
+    device->configure();
+
+    if (device->isIgnored()) {
+        LOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId, name.string());
+    } else {
+        LOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId, name.string(),
+                device->getSources());
+    }
+
+    bool added = false;
+    { // acquire device registry writer lock
+        RWLock::AutoWLock _wl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex < 0) {
+            mDevices.add(deviceId, device);
+            added = true;
+        }
+    } // release device registry writer lock
+
+    if (! added) {
+        LOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+        delete device;
+        return;
+    }
+}
+
+void InputReader::removeDevice(int32_t deviceId) {
+    bool removed = false;
+    InputDevice* device = NULL;
+    { // acquire device registry writer lock
+        RWLock::AutoWLock _wl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex >= 0) {
+            device = mDevices.valueAt(deviceIndex);
+            mDevices.removeItemsAt(deviceIndex, 1);
+            removed = true;
+        }
+    } // release device registry writer lock
+
+    if (! removed) {
+        LOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
+        return;
+    }
+
+    if (device->isIgnored()) {
+        LOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
+                device->getId(), device->getName().string());
+    } else {
+        LOGI("Device removed: id=%d, name='%s', sources=0x%08x",
+                device->getId(), device->getName().string(), device->getSources());
+    }
+
+    device->reset();
+
+    delete device;
+}
+
+InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+    InputDevice* device = new InputDevice(this, deviceId, name);
+
+    // Switch-like devices.
+    if (classes & INPUT_DEVICE_CLASS_SWITCH) {
+        device->addMapper(new SwitchInputMapper(device));
+    }
+
+    // Keyboard-like devices.
+    uint32_t keyboardSources = 0;
+    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+        keyboardSources |= AINPUT_SOURCE_KEYBOARD;
+    }
+    if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
+        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+    }
+    if (classes & INPUT_DEVICE_CLASS_DPAD) {
+        keyboardSources |= AINPUT_SOURCE_DPAD;
+    }
+
+    if (keyboardSources != 0) {
+        device->addMapper(new KeyboardInputMapper(device, keyboardSources, keyboardType));
+    }
+
+    // Cursor-like devices.
+    if (classes & INPUT_DEVICE_CLASS_CURSOR) {
+        device->addMapper(new CursorInputMapper(device));
+    }
+
+    // Touchscreen-like devices.
+    if (classes & INPUT_DEVICE_CLASS_TOUCHSCREEN_MT) {
+        device->addMapper(new MultiTouchInputMapper(device));
+    } else if (classes & INPUT_DEVICE_CLASS_TOUCHSCREEN) {
+        device->addMapper(new SingleTouchInputMapper(device));
+    }
+
+    return device;
+}
+
+void InputReader::consumeEvent(const RawEvent* rawEvent) {
+    int32_t deviceId = rawEvent->deviceId;
+
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex < 0) {
+            LOGW("Discarding event for unknown deviceId %d.", deviceId);
+            return;
+        }
+
+        InputDevice* device = mDevices.valueAt(deviceIndex);
+        if (device->isIgnored()) {
+            //LOGD("Discarding event for ignored deviceId %d.", deviceId);
+            return;
+        }
+
+        device->process(rawEvent);
+    } // release device registry reader lock
+}
+
+void InputReader::handleConfigurationChanged(nsecs_t when) {
+    // Reset global meta state because it depends on the list of all configured devices.
+    updateGlobalMetaState();
+
+    // Update input configuration.
+    updateInputConfiguration();
+
+    // Enqueue configuration changed.
+    mDispatcher->notifyConfigurationChanged(when);
+}
+
+void InputReader::configureExcludedDevices() {
+    Vector<String8> excludedDeviceNames;
+    mPolicy->getExcludedDeviceNames(excludedDeviceNames);
+
+    for (size_t i = 0; i < excludedDeviceNames.size(); i++) {
+        mEventHub->addExcludedDevice(excludedDeviceNames[i]);
+    }
+}
+
+void InputReader::updateGlobalMetaState() {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        mGlobalMetaState = 0;
+
+        { // acquire device registry reader lock
+            RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+            for (size_t i = 0; i < mDevices.size(); i++) {
+                InputDevice* device = mDevices.valueAt(i);
+                mGlobalMetaState |= device->getMetaState();
+            }
+        } // release device registry reader lock
+    } // release state lock
+}
+
+int32_t InputReader::getGlobalMetaState() {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        return mGlobalMetaState;
+    } // release state lock
+}
+
+void InputReader::updateInputConfiguration() {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH;
+        int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS;
+        int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV;
+        { // acquire device registry reader lock
+            RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+            InputDeviceInfo deviceInfo;
+            for (size_t i = 0; i < mDevices.size(); i++) {
+                InputDevice* device = mDevices.valueAt(i);
+                device->getDeviceInfo(& deviceInfo);
+                uint32_t sources = deviceInfo.getSources();
+
+                if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) {
+                    touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER;
+                }
+                if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) {
+                    navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL;
+                } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) {
+                    navigationConfig = InputConfiguration::NAVIGATION_DPAD;
+                }
+                if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+                    keyboardConfig = InputConfiguration::KEYBOARD_QWERTY;
+                }
+            }
+        } // release device registry reader lock
+
+        mInputConfiguration.touchScreen = touchScreenConfig;
+        mInputConfiguration.keyboard = keyboardConfig;
+        mInputConfiguration.navigation = navigationConfig;
+    } // release state lock
+}
+
+void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        *outConfiguration = mInputConfiguration;
+    } // release state lock
+}
+
+status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex < 0) {
+            return NAME_NOT_FOUND;
+        }
+
+        InputDevice* device = mDevices.valueAt(deviceIndex);
+        if (device->isIgnored()) {
+            return NAME_NOT_FOUND;
+        }
+
+        device->getDeviceInfo(outDeviceInfo);
+        return OK;
+    } // release device registy reader lock
+}
+
+void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
+    outDeviceIds.clear();
+
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        size_t numDevices = mDevices.size();
+        for (size_t i = 0; i < numDevices; i++) {
+            InputDevice* device = mDevices.valueAt(i);
+            if (! device->isIgnored()) {
+                outDeviceIds.add(device->getId());
+            }
+        }
+    } // release device registy reader lock
+}
+
+int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+        int32_t keyCode) {
+    return getState(deviceId, sourceMask, keyCode, & InputDevice::getKeyCodeState);
+}
+
+int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+        int32_t scanCode) {
+    return getState(deviceId, sourceMask, scanCode, & InputDevice::getScanCodeState);
+}
+
+int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
+    return getState(deviceId, sourceMask, switchCode, & InputDevice::getSwitchState);
+}
+
+int32_t InputReader::getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
+        GetStateFunc getStateFunc) {
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        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)) {
+                    result = (device->*getStateFunc)(sourceMask, code);
+                    if (result >= AKEY_STATE_DOWN) {
+                        return result;
+                    }
+                }
+            }
+        }
+        return result;
+    } // release device registy reader lock
+}
+
+bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
+        size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+    memset(outFlags, 0, numCodes);
+    return markSupportedKeyCodes(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+}
+
+bool InputReader::markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+        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;
+    } // release device registy reader lock
+}
+
+void InputReader::dump(String8& dump) {
+    mEventHub->dump(dump);
+    dump.append("\n");
+
+    dump.append("Input Reader State:\n");
+
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        for (size_t i = 0; i < mDevices.size(); i++) {
+            mDevices.valueAt(i)->dump(dump);
+        }
+    } // release device registy reader lock
+}
+
+
+// --- InputReaderThread ---
+
+InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
+        Thread(/*canCallJava*/ true), mReader(reader) {
+}
+
+InputReaderThread::~InputReaderThread() {
+}
+
+bool InputReaderThread::threadLoop() {
+    mReader->loopOnce();
+    return true;
+}
+
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name) :
+        mContext(context), mId(id), mName(name), mSources(0) {
+}
+
+InputDevice::~InputDevice() {
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        delete mMappers[i];
+    }
+    mMappers.clear();
+}
+
+static void dumpMotionRange(String8& dump, const InputDeviceInfo& deviceInfo,
+        int32_t rangeType, const char* name) {
+    const InputDeviceInfo::MotionRange* range = deviceInfo.getMotionRange(rangeType);
+    if (range) {
+        dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
+                name, range->min, range->max, range->flat, range->fuzz);
+    }
+}
+
+void InputDevice::dump(String8& dump) {
+    InputDeviceInfo deviceInfo;
+    getDeviceInfo(& deviceInfo);
+
+    dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
+            deviceInfo.getName().string());
+    dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+    dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+    if (!deviceInfo.getMotionRanges().isEmpty()) {
+        dump.append(INDENT2 "Motion Ranges:\n");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_X, "X");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_Y, "Y");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_PRESSURE, "Pressure");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_SIZE, "Size");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MAJOR, "TouchMajor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MINOR, "TouchMinor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MAJOR, "ToolMajor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MINOR, "ToolMinor");
+        dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_ORIENTATION, "Orientation");
+    }
+
+    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.add(mapper);
+}
+
+void InputDevice::configure() {
+    if (! isIgnored()) {
+        mContext->getEventHub()->getConfiguration(mId, &mConfiguration);
+    }
+
+    mSources = 0;
+
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->configure();
+        mSources |= mapper->getSources();
+    }
+}
+
+void InputDevice::reset() {
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->reset();
+    }
+}
+
+void InputDevice::process(const RawEvent* rawEvent) {
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->process(rawEvent);
+    }
+}
+
+void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
+    outDeviceInfo->initialize(mId, mName);
+
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        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;
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+            result = (mapper->*getStateFunc)(sourceMask, code);
+            if (result >= AKEY_STATE_DOWN) {
+                return result;
+            }
+        }
+    }
+    return result;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    bool result = false;
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+            result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+        }
+    }
+    return result;
+}
+
+int32_t InputDevice::getMetaState() {
+    int32_t result = 0;
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        result |= mapper->getMetaState();
+    }
+    return result;
+}
+
+
+// --- InputMapper ---
+
+InputMapper::InputMapper(InputDevice* device) :
+        mDevice(device), mContext(device->getContext()) {
+}
+
+InputMapper::~InputMapper() {
+}
+
+void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    info->addSource(getSources());
+}
+
+void InputMapper::dump(String8& dump) {
+}
+
+void InputMapper::configure() {
+}
+
+void InputMapper::reset() {
+}
+
+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;
+}
+
+int32_t InputMapper::getMetaState() {
+    return 0;
+}
+
+
+// --- SwitchInputMapper ---
+
+SwitchInputMapper::SwitchInputMapper(InputDevice* device) :
+        InputMapper(device) {
+}
+
+SwitchInputMapper::~SwitchInputMapper() {
+}
+
+uint32_t SwitchInputMapper::getSources() {
+    return 0;
+}
+
+void SwitchInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_SW:
+        processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value);
+        break;
+    }
+}
+
+void SwitchInputMapper::processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) {
+    getDispatcher()->notifySwitch(when, switchCode, switchValue, 0);
+}
+
+int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+    return getEventHub()->getSwitchState(getDeviceId(), switchCode);
+}
+
+
+// --- KeyboardInputMapper ---
+
+KeyboardInputMapper::KeyboardInputMapper(InputDevice* device,
+        uint32_t sources, int32_t keyboardType) :
+        InputMapper(device), mSources(sources),
+        mKeyboardType(keyboardType) {
+    initializeLocked();
+}
+
+KeyboardInputMapper::~KeyboardInputMapper() {
+}
+
+void KeyboardInputMapper::initializeLocked() {
+    mLocked.metaState = AMETA_NONE;
+    mLocked.downTime = 0;
+}
+
+uint32_t KeyboardInputMapper::getSources() {
+    return mSources;
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    info->setKeyboardType(mKeyboardType);
+}
+
+void KeyboardInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Keyboard Input Mapper:\n");
+        dumpParameters(dump);
+        dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+        dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mLocked.keyDowns.size());
+        dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mLocked.metaState);
+        dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+    } // release lock
+}
+
+
+void KeyboardInputMapper::configure() {
+    InputMapper::configure();
+
+    // Configure basic parameters.
+    configureParameters();
+
+    // Reset LEDs.
+    {
+        AutoMutex _l(mLock);
+        resetLedStateLocked();
+    }
+}
+
+void KeyboardInputMapper::configureParameters() {
+    mParameters.orientationAware = false;
+    getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"),
+            mParameters.orientationAware);
+
+    mParameters.associatedDisplayId = mParameters.orientationAware ? 0 : -1;
+}
+
+void KeyboardInputMapper::dumpParameters(String8& dump) {
+    dump.append(INDENT3 "Parameters:\n");
+    dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
+            mParameters.associatedDisplayId);
+    dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+            toString(mParameters.orientationAware));
+}
+
+void KeyboardInputMapper::reset() {
+    for (;;) {
+        int32_t keyCode, scanCode;
+        { // acquire lock
+            AutoMutex _l(mLock);
+
+            // Synthesize key up event on reset if keys are currently down.
+            if (mLocked.keyDowns.isEmpty()) {
+                initializeLocked();
+                resetLedStateLocked();
+                break; // done
+            }
+
+            const KeyDown& keyDown = mLocked.keyDowns.top();
+            keyCode = keyDown.keyCode;
+            scanCode = keyDown.scanCode;
+        } // release lock
+
+        nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+        processKey(when, false, keyCode, scanCode, 0);
+    }
+
+    InputMapper::reset();
+    getContext()->updateGlobalMetaState();
+}
+
+void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_KEY: {
+        int32_t scanCode = rawEvent->scanCode;
+        if (isKeyboardOrGamepadKey(scanCode)) {
+            processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode,
+                    rawEvent->flags);
+        }
+        break;
+    }
+    }
+}
+
+bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
+    return scanCode < BTN_MOUSE
+        || scanCode >= KEY_OK
+        || (scanCode >= BTN_GAMEPAD && scanCode < BTN_DIGI);
+}
+
+void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
+        int32_t scanCode, uint32_t policyFlags) {
+    int32_t newMetaState;
+    nsecs_t downTime;
+    bool metaStateChanged = false;
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (down) {
+            // Rotate key codes according to orientation if needed.
+            // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.
+            if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0) {
+                int32_t orientation;
+                if (!getPolicy()->getDisplayInfo(mParameters.associatedDisplayId,
+                        NULL, NULL, & orientation)) {
+                    orientation = DISPLAY_ORIENTATION_0;
+                }
+
+                keyCode = rotateKeyCode(keyCode, orientation);
+            }
+
+            // Add key down.
+            ssize_t keyDownIndex = findKeyDownLocked(scanCode);
+            if (keyDownIndex >= 0) {
+                // key repeat, be sure to use same keycode as before in case of rotation
+                keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
+            } else {
+                // key down
+                mLocked.keyDowns.push();
+                KeyDown& keyDown = mLocked.keyDowns.editTop();
+                keyDown.keyCode = keyCode;
+                keyDown.scanCode = scanCode;
+            }
+
+            mLocked.downTime = when;
+        } else {
+            // Remove key down.
+            ssize_t keyDownIndex = findKeyDownLocked(scanCode);
+            if (keyDownIndex >= 0) {
+                // key up, be sure to use same keycode as before in case of rotation
+                keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
+                mLocked.keyDowns.removeAt(size_t(keyDownIndex));
+            } else {
+                // key was not actually down
+                LOGI("Dropping key up from device %s because the key was not down.  "
+                        "keyCode=%d, scanCode=%d",
+                        getDeviceName().string(), keyCode, scanCode);
+                return;
+            }
+        }
+
+        int32_t oldMetaState = mLocked.metaState;
+        newMetaState = updateMetaState(keyCode, down, oldMetaState);
+        if (oldMetaState != newMetaState) {
+            mLocked.metaState = newMetaState;
+            metaStateChanged = true;
+            updateLedStateLocked(false);
+        }
+
+        downTime = mLocked.downTime;
+    } // release lock
+
+    if (metaStateChanged) {
+        getContext()->updateGlobalMetaState();
+    }
+
+    if (policyFlags & POLICY_FLAG_FUNCTION) {
+        newMetaState |= AMETA_FUNCTION_ON;
+    }
+    getDispatcher()->notifyKey(when, getDeviceId(), mSources, policyFlags,
+            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
+}
+
+ssize_t KeyboardInputMapper::findKeyDownLocked(int32_t scanCode) {
+    size_t n = mLocked.keyDowns.size();
+    for (size_t i = 0; i < n; i++) {
+        if (mLocked.keyDowns[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() {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        return mLocked.metaState;
+    } // release lock
+}
+
+void KeyboardInputMapper::resetLedStateLocked() {
+    initializeLedStateLocked(mLocked.capsLockLedState, LED_CAPSL);
+    initializeLedStateLocked(mLocked.numLockLedState, LED_NUML);
+    initializeLedStateLocked(mLocked.scrollLockLedState, LED_SCROLLL);
+
+    updateLedStateLocked(true);
+}
+
+void KeyboardInputMapper::initializeLedStateLocked(LockedState::LedState& ledState, int32_t led) {
+    ledState.avail = getEventHub()->hasLed(getDeviceId(), led);
+    ledState.on = false;
+}
+
+void KeyboardInputMapper::updateLedStateLocked(bool reset) {
+    updateLedStateForModifierLocked(mLocked.capsLockLedState, LED_CAPSL,
+            AMETA_CAPS_LOCK_ON, reset);
+    updateLedStateForModifierLocked(mLocked.numLockLedState, LED_NUML,
+            AMETA_NUM_LOCK_ON, reset);
+    updateLedStateForModifierLocked(mLocked.scrollLockLedState, LED_SCROLLL,
+            AMETA_SCROLL_LOCK_ON, reset);
+}
+
+void KeyboardInputMapper::updateLedStateForModifierLocked(LockedState::LedState& ledState,
+        int32_t led, int32_t modifier, bool reset) {
+    if (ledState.avail) {
+        bool desiredState = (mLocked.metaState & modifier) != 0;
+        if (reset || ledState.on != desiredState) {
+            getEventHub()->setLedState(getDeviceId(), led, desiredState);
+            ledState.on = desiredState;
+        }
+    }
+}
+
+
+// --- CursorInputMapper ---
+
+CursorInputMapper::CursorInputMapper(InputDevice* device) :
+        InputMapper(device) {
+    initializeLocked();
+}
+
+CursorInputMapper::~CursorInputMapper() {
+}
+
+uint32_t CursorInputMapper::getSources() {
+    return mSources;
+}
+
+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(AINPUT_MOTION_RANGE_X, minX, maxX, 0.0f, 0.0f);
+            info->addMotionRange(AINPUT_MOTION_RANGE_Y, minY, maxY, 0.0f, 0.0f);
+        }
+    } else {
+        info->addMotionRange(AINPUT_MOTION_RANGE_X, -1.0f, 1.0f, 0.0f, mXScale);
+        info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale);
+    }
+    info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE, 0.0f, 1.0f, 0.0f, 0.0f);
+}
+
+void CursorInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Cursor Input Mapper:\n");
+        dumpParameters(dump);
+        dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+        dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+        dump.appendFormat(INDENT3 "Down: %s\n", toString(mLocked.down));
+        dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+    } // release lock
+}
+
+void CursorInputMapper::configure() {
+    InputMapper::configure();
+
+    // Configure basic parameters.
+    configureParameters();
+
+    // Configure device mode.
+    switch (mParameters.mode) {
+    case Parameters::MODE_POINTER:
+        mSources = AINPUT_SOURCE_MOUSE;
+        mXPrecision = 1.0f;
+        mYPrecision = 1.0f;
+        mXScale = 1.0f;
+        mYScale = 1.0f;
+        mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+        break;
+    case Parameters::MODE_NAVIGATION:
+        mSources = AINPUT_SOURCE_TRACKBALL;
+        mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+        mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+        mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+        mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+        break;
+    }
+}
+
+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") {
+            LOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
+        }
+    }
+
+    mParameters.orientationAware = false;
+    getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
+            mParameters.orientationAware);
+
+    mParameters.associatedDisplayId = mParameters.mode == Parameters::MODE_POINTER
+            || mParameters.orientationAware ? 0 : -1;
+}
+
+void CursorInputMapper::dumpParameters(String8& dump) {
+    dump.append(INDENT3 "Parameters:\n");
+    dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
+            mParameters.associatedDisplayId);
+
+    switch (mParameters.mode) {
+    case Parameters::MODE_POINTER:
+        dump.append(INDENT4 "Mode: pointer\n");
+        break;
+    case Parameters::MODE_NAVIGATION:
+        dump.append(INDENT4 "Mode: navigation\n");
+        break;
+    default:
+        assert(false);
+    }
+
+    dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+            toString(mParameters.orientationAware));
+}
+
+void CursorInputMapper::initializeLocked() {
+    mAccumulator.clear();
+
+    mLocked.down = false;
+    mLocked.downTime = 0;
+}
+
+void CursorInputMapper::reset() {
+    for (;;) {
+        { // acquire lock
+            AutoMutex _l(mLock);
+
+            if (! mLocked.down) {
+                initializeLocked();
+                break; // done
+            }
+        } // release lock
+
+        // Synthesize button up event on reset.
+        nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+        mAccumulator.fields = Accumulator::FIELD_BTN_MOUSE;
+        mAccumulator.btnMouse = false;
+        sync(when);
+    }
+
+    InputMapper::reset();
+}
+
+void CursorInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_KEY:
+        switch (rawEvent->scanCode) {
+        case BTN_MOUSE:
+            mAccumulator.fields |= Accumulator::FIELD_BTN_MOUSE;
+            mAccumulator.btnMouse = rawEvent->value != 0;
+            // Sync now since BTN_MOUSE is not necessarily followed by SYN_REPORT and
+            // we need to ensure that we report the up/down promptly.
+            sync(rawEvent->when);
+            break;
+        }
+        break;
+
+    case EV_REL:
+        switch (rawEvent->scanCode) {
+        case REL_X:
+            mAccumulator.fields |= Accumulator::FIELD_REL_X;
+            mAccumulator.relX = rawEvent->value;
+            break;
+        case REL_Y:
+            mAccumulator.fields |= Accumulator::FIELD_REL_Y;
+            mAccumulator.relY = rawEvent->value;
+            break;
+        }
+        break;
+
+    case EV_SYN:
+        switch (rawEvent->scanCode) {
+        case SYN_REPORT:
+            sync(rawEvent->when);
+            break;
+        }
+        break;
+    }
+}
+
+void CursorInputMapper::sync(nsecs_t when) {
+    uint32_t fields = mAccumulator.fields;
+    if (fields == 0) {
+        return; // no new state changes, so nothing to do
+    }
+
+    int motionEventAction;
+    PointerCoords pointerCoords;
+    nsecs_t downTime;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        bool downChanged = fields & Accumulator::FIELD_BTN_MOUSE;
+
+        if (downChanged) {
+            if (mAccumulator.btnMouse) {
+                mLocked.down = true;
+                mLocked.downTime = when;
+            } else {
+                mLocked.down = false;
+            }
+        }
+
+        downTime = mLocked.downTime;
+        float deltaX = fields & Accumulator::FIELD_REL_X ? mAccumulator.relX * mXScale : 0.0f;
+        float deltaY = fields & Accumulator::FIELD_REL_Y ? mAccumulator.relY * mYScale : 0.0f;
+
+        if (downChanged) {
+            motionEventAction = mLocked.down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+        } else {
+            motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+        }
+
+        if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0
+                && (deltaX != 0.0f || deltaY != 0.0f)) {
+            // Rotate motion based on display orientation if needed.
+            // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.
+            int32_t orientation;
+            if (! getPolicy()->getDisplayInfo(mParameters.associatedDisplayId,
+                    NULL, NULL, & orientation)) {
+                orientation = DISPLAY_ORIENTATION_0;
+            }
+
+            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;
+            }
+        }
+
+        if (mPointerController != NULL) {
+            mPointerController->move(deltaX, deltaY);
+            if (downChanged) {
+                mPointerController->setButtonState(mLocked.down ? POINTER_BUTTON_1 : 0);
+            }
+            mPointerController->getPosition(&pointerCoords.x, &pointerCoords.y);
+        } else {
+            pointerCoords.x = deltaX;
+            pointerCoords.y = deltaY;
+        }
+
+        pointerCoords.pressure = mLocked.down ? 1.0f : 0.0f;
+        pointerCoords.size = 0;
+        pointerCoords.touchMajor = 0;
+        pointerCoords.touchMinor = 0;
+        pointerCoords.toolMajor = 0;
+        pointerCoords.toolMinor = 0;
+        pointerCoords.orientation = 0;
+    } // release lock
+
+    int32_t metaState = mContext->getGlobalMetaState();
+    int32_t pointerId = 0;
+    getDispatcher()->notifyMotion(when, getDeviceId(), mSources, 0,
+            motionEventAction, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
+            1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime);
+
+    mAccumulator.clear();
+}
+
+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;
+    }
+}
+
+
+// --- TouchInputMapper ---
+
+TouchInputMapper::TouchInputMapper(InputDevice* device) :
+        InputMapper(device) {
+    mLocked.surfaceOrientation = -1;
+    mLocked.surfaceWidth = -1;
+    mLocked.surfaceHeight = -1;
+
+    initializeLocked();
+}
+
+TouchInputMapper::~TouchInputMapper() {
+}
+
+uint32_t TouchInputMapper::getSources() {
+    return mSources;
+}
+
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        // Ensure surface information is up to date so that orientation changes are
+        // noticed immediately.
+        configureSurfaceLocked();
+
+        info->addMotionRange(AINPUT_MOTION_RANGE_X, mLocked.orientedRanges.x);
+        info->addMotionRange(AINPUT_MOTION_RANGE_Y, mLocked.orientedRanges.y);
+
+        if (mLocked.orientedRanges.havePressure) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE,
+                    mLocked.orientedRanges.pressure);
+        }
+
+        if (mLocked.orientedRanges.haveSize) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_SIZE,
+                    mLocked.orientedRanges.size);
+        }
+
+        if (mLocked.orientedRanges.haveTouchSize) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MAJOR,
+                    mLocked.orientedRanges.touchMajor);
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MINOR,
+                    mLocked.orientedRanges.touchMinor);
+        }
+
+        if (mLocked.orientedRanges.haveToolSize) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MAJOR,
+                    mLocked.orientedRanges.toolMajor);
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MINOR,
+                    mLocked.orientedRanges.toolMinor);
+        }
+
+        if (mLocked.orientedRanges.haveOrientation) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_ORIENTATION,
+                    mLocked.orientedRanges.orientation);
+        }
+    } // release lock
+}
+
+void TouchInputMapper::dump(String8& dump) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+        dump.append(INDENT2 "Touch Input Mapper:\n");
+        dumpParameters(dump);
+        dumpVirtualKeysLocked(dump);
+        dumpRawAxes(dump);
+        dumpCalibration(dump);
+        dumpSurfaceLocked(dump);
+        dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n");
+        dump.appendFormat(INDENT4 "XOrigin: %d\n", mLocked.xOrigin);
+        dump.appendFormat(INDENT4 "YOrigin: %d\n", mLocked.yOrigin);
+        dump.appendFormat(INDENT4 "XScale: %0.3f\n", mLocked.xScale);
+        dump.appendFormat(INDENT4 "YScale: %0.3f\n", mLocked.yScale);
+        dump.appendFormat(INDENT4 "XPrecision: %0.3f\n", mLocked.xPrecision);
+        dump.appendFormat(INDENT4 "YPrecision: %0.3f\n", mLocked.yPrecision);
+        dump.appendFormat(INDENT4 "GeometricScale: %0.3f\n", mLocked.geometricScale);
+        dump.appendFormat(INDENT4 "ToolSizeLinearScale: %0.3f\n", mLocked.toolSizeLinearScale);
+        dump.appendFormat(INDENT4 "ToolSizeLinearBias: %0.3f\n", mLocked.toolSizeLinearBias);
+        dump.appendFormat(INDENT4 "ToolSizeAreaScale: %0.3f\n", mLocked.toolSizeAreaScale);
+        dump.appendFormat(INDENT4 "ToolSizeAreaBias: %0.3f\n", mLocked.toolSizeAreaBias);
+        dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mLocked.pressureScale);
+        dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mLocked.sizeScale);
+        dump.appendFormat(INDENT4 "OrientationSCale: %0.3f\n", mLocked.orientationScale);
+    } // release lock
+}
+
+void TouchInputMapper::initializeLocked() {
+    mCurrentTouch.clear();
+    mLastTouch.clear();
+    mDownTime = 0;
+
+    for (uint32_t i = 0; i < MAX_POINTERS; i++) {
+        mAveragingTouchFilter.historyStart[i] = 0;
+        mAveragingTouchFilter.historyEnd[i] = 0;
+    }
+
+    mJumpyTouchFilter.jumpyPointsDropped = 0;
+
+    mLocked.currentVirtualKey.down = false;
+
+    mLocked.orientedRanges.havePressure = false;
+    mLocked.orientedRanges.haveSize = false;
+    mLocked.orientedRanges.haveTouchSize = false;
+    mLocked.orientedRanges.haveToolSize = false;
+    mLocked.orientedRanges.haveOrientation = false;
+}
+
+void TouchInputMapper::configure() {
+    InputMapper::configure();
+
+    // Configure basic parameters.
+    configureParameters();
+
+    // Configure sources.
+    switch (mParameters.deviceType) {
+    case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+        mSources = AINPUT_SOURCE_TOUCHSCREEN;
+        break;
+    case Parameters::DEVICE_TYPE_TOUCH_PAD:
+        mSources = AINPUT_SOURCE_TOUCHPAD;
+        break;
+    default:
+        assert(false);
+    }
+
+    // Configure absolute axis information.
+    configureRawAxes();
+
+    // Prepare input device calibration.
+    parseCalibration();
+    resolveCalibration();
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+         // Configure surface dimensions and orientation.
+        configureSurfaceLocked();
+    } // release lock
+}
+
+void TouchInputMapper::configureParameters() {
+    mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents();
+    mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents();
+    mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
+
+    String8 deviceTypeString;
+    mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+    if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),
+            deviceTypeString)) {
+        if (deviceTypeString == "touchPad") {
+            mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+        } else if (deviceTypeString != "touchScreen") {
+            LOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
+        }
+    }
+    bool isTouchScreen = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+
+    mParameters.orientationAware = isTouchScreen;
+    getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
+            mParameters.orientationAware);
+
+    mParameters.associatedDisplayId = mParameters.orientationAware || isTouchScreen ? 0 : -1;
+}
+
+void TouchInputMapper::dumpParameters(String8& dump) {
+    dump.append(INDENT3 "Parameters:\n");
+
+    switch (mParameters.deviceType) {
+    case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+        dump.append(INDENT4 "DeviceType: touchScreen\n");
+        break;
+    case Parameters::DEVICE_TYPE_TOUCH_PAD:
+        dump.append(INDENT4 "DeviceType: touchPad\n");
+        break;
+    default:
+        assert(false);
+    }
+
+    dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
+            mParameters.associatedDisplayId);
+    dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+            toString(mParameters.orientationAware));
+
+    dump.appendFormat(INDENT4 "UseBadTouchFilter: %s\n",
+            toString(mParameters.useBadTouchFilter));
+    dump.appendFormat(INDENT4 "UseAveragingTouchFilter: %s\n",
+            toString(mParameters.useAveragingTouchFilter));
+    dump.appendFormat(INDENT4 "UseJumpyTouchFilter: %s\n",
+            toString(mParameters.useJumpyTouchFilter));
+}
+
+void TouchInputMapper::configureRawAxes() {
+    mRawAxes.x.clear();
+    mRawAxes.y.clear();
+    mRawAxes.pressure.clear();
+    mRawAxes.touchMajor.clear();
+    mRawAxes.touchMinor.clear();
+    mRawAxes.toolMajor.clear();
+    mRawAxes.toolMinor.clear();
+    mRawAxes.orientation.clear();
+}
+
+static void dumpAxisInfo(String8& dump, RawAbsoluteAxisInfo axis, const char* name) {
+    if (axis.valid) {
+        dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n",
+                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
+    } else {
+        dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+    }
+}
+
+void TouchInputMapper::dumpRawAxes(String8& dump) {
+    dump.append(INDENT3 "Raw Axes:\n");
+    dumpAxisInfo(dump, mRawAxes.x, "X");
+    dumpAxisInfo(dump, mRawAxes.y, "Y");
+    dumpAxisInfo(dump, mRawAxes.pressure, "Pressure");
+    dumpAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor");
+    dumpAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor");
+    dumpAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
+    dumpAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
+    dumpAxisInfo(dump, mRawAxes.orientation, "Orientation");
+}
+
+bool TouchInputMapper::configureSurfaceLocked() {
+    // Update orientation and dimensions if needed.
+    int32_t orientation = DISPLAY_ORIENTATION_0;
+    int32_t width = mRawAxes.x.getRange();
+    int32_t height = mRawAxes.y.getRange();
+
+    if (mParameters.associatedDisplayId >= 0) {
+        bool wantSize = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+        bool wantOrientation = mParameters.orientationAware;
+
+        // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.
+        if (! getPolicy()->getDisplayInfo(mParameters.associatedDisplayId,
+                wantSize ? &width : NULL, wantSize ? &height : NULL,
+                wantOrientation ? &orientation : NULL)) {
+            return false;
+        }
+    }
+
+    bool orientationChanged = mLocked.surfaceOrientation != orientation;
+    if (orientationChanged) {
+        mLocked.surfaceOrientation = orientation;
+    }
+
+    bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
+    if (sizeChanged) {
+        LOGI("Device reconfigured: id=%d, name='%s', display size is now %dx%d",
+                getDeviceId(), getDeviceName().string(), width, height);
+
+        mLocked.surfaceWidth = width;
+        mLocked.surfaceHeight = height;
+
+        // Configure X and Y factors.
+        if (mRawAxes.x.valid && mRawAxes.y.valid) {
+            mLocked.xOrigin = mCalibration.haveXOrigin
+                    ? mCalibration.xOrigin
+                    : mRawAxes.x.minValue;
+            mLocked.yOrigin = mCalibration.haveYOrigin
+                    ? mCalibration.yOrigin
+                    : mRawAxes.y.minValue;
+            mLocked.xScale = mCalibration.haveXScale
+                    ? mCalibration.xScale
+                    : float(width) / mRawAxes.x.getRange();
+            mLocked.yScale = mCalibration.haveYScale
+                    ? mCalibration.yScale
+                    : float(height) / mRawAxes.y.getRange();
+            mLocked.xPrecision = 1.0f / mLocked.xScale;
+            mLocked.yPrecision = 1.0f / mLocked.yScale;
+
+            configureVirtualKeysLocked();
+        } else {
+            LOGW(INDENT "Touch device did not report support for X or Y axis!");
+            mLocked.xOrigin = 0;
+            mLocked.yOrigin = 0;
+            mLocked.xScale = 1.0f;
+            mLocked.yScale = 1.0f;
+            mLocked.xPrecision = 1.0f;
+            mLocked.yPrecision = 1.0f;
+        }
+
+        // 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.
+        mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale);
+
+        // Size of diagonal axis.
+        float diagonalSize = pythag(width, height);
+
+        // TouchMajor and TouchMinor factors.
+        if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) {
+            mLocked.orientedRanges.haveTouchSize = true;
+            mLocked.orientedRanges.touchMajor.min = 0;
+            mLocked.orientedRanges.touchMajor.max = diagonalSize;
+            mLocked.orientedRanges.touchMajor.flat = 0;
+            mLocked.orientedRanges.touchMajor.fuzz = 0;
+            mLocked.orientedRanges.touchMinor = mLocked.orientedRanges.touchMajor;
+        }
+
+        // ToolMajor and ToolMinor factors.
+        mLocked.toolSizeLinearScale = 0;
+        mLocked.toolSizeLinearBias = 0;
+        mLocked.toolSizeAreaScale = 0;
+        mLocked.toolSizeAreaBias = 0;
+        if (mCalibration.toolSizeCalibration != Calibration::TOOL_SIZE_CALIBRATION_NONE) {
+            if (mCalibration.toolSizeCalibration == Calibration::TOOL_SIZE_CALIBRATION_LINEAR) {
+                if (mCalibration.haveToolSizeLinearScale) {
+                    mLocked.toolSizeLinearScale = mCalibration.toolSizeLinearScale;
+                } else if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+                    mLocked.toolSizeLinearScale = float(min(width, height))
+                            / mRawAxes.toolMajor.maxValue;
+                }
+
+                if (mCalibration.haveToolSizeLinearBias) {
+                    mLocked.toolSizeLinearBias = mCalibration.toolSizeLinearBias;
+                }
+            } else if (mCalibration.toolSizeCalibration ==
+                    Calibration::TOOL_SIZE_CALIBRATION_AREA) {
+                if (mCalibration.haveToolSizeLinearScale) {
+                    mLocked.toolSizeLinearScale = mCalibration.toolSizeLinearScale;
+                } else {
+                    mLocked.toolSizeLinearScale = min(width, height);
+                }
+
+                if (mCalibration.haveToolSizeLinearBias) {
+                    mLocked.toolSizeLinearBias = mCalibration.toolSizeLinearBias;
+                }
+
+                if (mCalibration.haveToolSizeAreaScale) {
+                    mLocked.toolSizeAreaScale = mCalibration.toolSizeAreaScale;
+                } else if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+                    mLocked.toolSizeAreaScale = 1.0f / mRawAxes.toolMajor.maxValue;
+                }
+
+                if (mCalibration.haveToolSizeAreaBias) {
+                    mLocked.toolSizeAreaBias = mCalibration.toolSizeAreaBias;
+                }
+            }
+
+            mLocked.orientedRanges.haveToolSize = true;
+            mLocked.orientedRanges.toolMajor.min = 0;
+            mLocked.orientedRanges.toolMajor.max = diagonalSize;
+            mLocked.orientedRanges.toolMajor.flat = 0;
+            mLocked.orientedRanges.toolMajor.fuzz = 0;
+            mLocked.orientedRanges.toolMinor = mLocked.orientedRanges.toolMajor;
+        }
+
+        // Pressure factors.
+        mLocked.pressureScale = 0;
+        if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE) {
+            RawAbsoluteAxisInfo rawPressureAxis;
+            switch (mCalibration.pressureSource) {
+            case Calibration::PRESSURE_SOURCE_PRESSURE:
+                rawPressureAxis = mRawAxes.pressure;
+                break;
+            case Calibration::PRESSURE_SOURCE_TOUCH:
+                rawPressureAxis = mRawAxes.touchMajor;
+                break;
+            default:
+                rawPressureAxis.clear();
+            }
+
+            if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL
+                    || mCalibration.pressureCalibration
+                            == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
+                if (mCalibration.havePressureScale) {
+                    mLocked.pressureScale = mCalibration.pressureScale;
+                } else if (rawPressureAxis.valid && rawPressureAxis.maxValue != 0) {
+                    mLocked.pressureScale = 1.0f / rawPressureAxis.maxValue;
+                }
+            }
+
+            mLocked.orientedRanges.havePressure = true;
+            mLocked.orientedRanges.pressure.min = 0;
+            mLocked.orientedRanges.pressure.max = 1.0;
+            mLocked.orientedRanges.pressure.flat = 0;
+            mLocked.orientedRanges.pressure.fuzz = 0;
+        }
+
+        // Size factors.
+        mLocked.sizeScale = 0;
+        if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
+            if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_NORMALIZED) {
+                if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+                    mLocked.sizeScale = 1.0f / mRawAxes.toolMajor.maxValue;
+                }
+            }
+
+            mLocked.orientedRanges.haveSize = true;
+            mLocked.orientedRanges.size.min = 0;
+            mLocked.orientedRanges.size.max = 1.0;
+            mLocked.orientedRanges.size.flat = 0;
+            mLocked.orientedRanges.size.fuzz = 0;
+        }
+
+        // Orientation
+        mLocked.orientationScale = 0;
+        if (mCalibration.orientationCalibration != Calibration::ORIENTATION_CALIBRATION_NONE) {
+            if (mCalibration.orientationCalibration
+                    == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
+                if (mRawAxes.orientation.valid && mRawAxes.orientation.maxValue != 0) {
+                    mLocked.orientationScale = float(M_PI_2) / mRawAxes.orientation.maxValue;
+                }
+            }
+
+            mLocked.orientedRanges.orientation.min = - M_PI_2;
+            mLocked.orientedRanges.orientation.max = M_PI_2;
+            mLocked.orientedRanges.orientation.flat = 0;
+            mLocked.orientedRanges.orientation.fuzz = 0;
+        }
+    }
+
+    if (orientationChanged || sizeChanged) {
+        // Compute oriented surface dimensions, precision, and scales.
+        float orientedXScale, orientedYScale;
+        switch (mLocked.surfaceOrientation) {
+        case DISPLAY_ORIENTATION_90:
+        case DISPLAY_ORIENTATION_270:
+            mLocked.orientedSurfaceWidth = mLocked.surfaceHeight;
+            mLocked.orientedSurfaceHeight = mLocked.surfaceWidth;
+            mLocked.orientedXPrecision = mLocked.yPrecision;
+            mLocked.orientedYPrecision = mLocked.xPrecision;
+            orientedXScale = mLocked.yScale;
+            orientedYScale = mLocked.xScale;
+            break;
+        default:
+            mLocked.orientedSurfaceWidth = mLocked.surfaceWidth;
+            mLocked.orientedSurfaceHeight = mLocked.surfaceHeight;
+            mLocked.orientedXPrecision = mLocked.xPrecision;
+            mLocked.orientedYPrecision = mLocked.yPrecision;
+            orientedXScale = mLocked.xScale;
+            orientedYScale = mLocked.yScale;
+            break;
+        }
+
+        // Configure position ranges.
+        mLocked.orientedRanges.x.min = 0;
+        mLocked.orientedRanges.x.max = mLocked.orientedSurfaceWidth;
+        mLocked.orientedRanges.x.flat = 0;
+        mLocked.orientedRanges.x.fuzz = orientedXScale;
+
+        mLocked.orientedRanges.y.min = 0;
+        mLocked.orientedRanges.y.max = mLocked.orientedSurfaceHeight;
+        mLocked.orientedRanges.y.flat = 0;
+        mLocked.orientedRanges.y.fuzz = orientedYScale;
+    }
+
+    return true;
+}
+
+void TouchInputMapper::dumpSurfaceLocked(String8& dump) {
+    dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mLocked.surfaceWidth);
+    dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mLocked.surfaceHeight);
+    dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mLocked.surfaceOrientation);
+}
+
+void TouchInputMapper::configureVirtualKeysLocked() {
+    assert(mRawAxes.x.valid && mRawAxes.y.valid);
+
+    Vector<VirtualKeyDefinition> virtualKeyDefinitions;
+    getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions);
+
+    mLocked.virtualKeys.clear();
+
+    if (virtualKeyDefinitions.size() == 0) {
+        return;
+    }
+
+    mLocked.virtualKeys.setCapacity(virtualKeyDefinitions.size());
+
+    int32_t touchScreenLeft = mRawAxes.x.minValue;
+    int32_t touchScreenTop = mRawAxes.y.minValue;
+    int32_t touchScreenWidth = mRawAxes.x.getRange();
+    int32_t touchScreenHeight = mRawAxes.y.getRange();
+
+    for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
+        const VirtualKeyDefinition& virtualKeyDefinition =
+                virtualKeyDefinitions[i];
+
+        mLocked.virtualKeys.add();
+        VirtualKey& virtualKey = mLocked.virtualKeys.editTop();
+
+        virtualKey.scanCode = virtualKeyDefinition.scanCode;
+        int32_t keyCode;
+        uint32_t flags;
+        if (getEventHub()->scancodeToKeycode(getDeviceId(), virtualKey.scanCode,
+                & keyCode, & flags)) {
+            LOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
+                    virtualKey.scanCode);
+            mLocked.virtualKeys.pop(); // drop the key
+            continue;
+        }
+
+        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 / mLocked.surfaceWidth + touchScreenLeft;
+        virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
+                * touchScreenWidth / mLocked.surfaceWidth + touchScreenLeft;
+        virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
+                * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
+        virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
+                * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
+
+    }
+}
+
+void TouchInputMapper::dumpVirtualKeysLocked(String8& dump) {
+    if (!mLocked.virtualKeys.isEmpty()) {
+        dump.append(INDENT3 "Virtual Keys:\n");
+
+        for (size_t i = 0; i < mLocked.virtualKeys.size(); i++) {
+            const VirtualKey& virtualKey = mLocked.virtualKeys.itemAt(i);
+            dump.appendFormat(INDENT4 "%d: 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;
+
+    // Position
+    out.haveXOrigin = in.tryGetProperty(String8("touch.position.xOrigin"), out.xOrigin);
+    out.haveYOrigin = in.tryGetProperty(String8("touch.position.yOrigin"), out.yOrigin);
+    out.haveXScale = in.tryGetProperty(String8("touch.position.xScale"), out.xScale);
+    out.haveYScale = in.tryGetProperty(String8("touch.position.yScale"), out.yScale);
+
+    // Touch Size
+    out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_DEFAULT;
+    String8 touchSizeCalibrationString;
+    if (in.tryGetProperty(String8("touch.touchSize.calibration"), touchSizeCalibrationString)) {
+        if (touchSizeCalibrationString == "none") {
+            out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_NONE;
+        } else if (touchSizeCalibrationString == "geometric") {
+            out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC;
+        } else if (touchSizeCalibrationString == "pressure") {
+            out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE;
+        } else if (touchSizeCalibrationString != "default") {
+            LOGW("Invalid value for touch.touchSize.calibration: '%s'",
+                    touchSizeCalibrationString.string());
+        }
+    }
+
+    // Tool Size
+    out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_DEFAULT;
+    String8 toolSizeCalibrationString;
+    if (in.tryGetProperty(String8("touch.toolSize.calibration"), toolSizeCalibrationString)) {
+        if (toolSizeCalibrationString == "none") {
+            out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_NONE;
+        } else if (toolSizeCalibrationString == "geometric") {
+            out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC;
+        } else if (toolSizeCalibrationString == "linear") {
+            out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_LINEAR;
+        } else if (toolSizeCalibrationString == "area") {
+            out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_AREA;
+        } else if (toolSizeCalibrationString != "default") {
+            LOGW("Invalid value for touch.toolSize.calibration: '%s'",
+                    toolSizeCalibrationString.string());
+        }
+    }
+
+    out.haveToolSizeLinearScale = in.tryGetProperty(String8("touch.toolSize.linearScale"),
+            out.toolSizeLinearScale);
+    out.haveToolSizeLinearBias = in.tryGetProperty(String8("touch.toolSize.linearBias"),
+            out.toolSizeLinearBias);
+    out.haveToolSizeAreaScale = in.tryGetProperty(String8("touch.toolSize.areaScale"),
+            out.toolSizeAreaScale);
+    out.haveToolSizeAreaBias = in.tryGetProperty(String8("touch.toolSize.areaBias"),
+            out.toolSizeAreaBias);
+    out.haveToolSizeIsSummed = in.tryGetProperty(String8("touch.toolSize.isSummed"),
+            out.toolSizeIsSummed);
+
+    // 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") {
+            LOGW("Invalid value for touch.pressure.calibration: '%s'",
+                    pressureCalibrationString.string());
+        }
+    }
+
+    out.pressureSource = Calibration::PRESSURE_SOURCE_DEFAULT;
+    String8 pressureSourceString;
+    if (in.tryGetProperty(String8("touch.pressure.source"), pressureSourceString)) {
+        if (pressureSourceString == "pressure") {
+            out.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE;
+        } else if (pressureSourceString == "touch") {
+            out.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH;
+        } else if (pressureSourceString != "default") {
+            LOGW("Invalid value for touch.pressure.source: '%s'",
+                    pressureSourceString.string());
+        }
+    }
+
+    out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"),
+            out.pressureScale);
+
+    // 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 == "normalized") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED;
+        } else if (sizeCalibrationString != "default") {
+            LOGW("Invalid value for touch.size.calibration: '%s'",
+                    sizeCalibrationString.string());
+        }
+    }
+
+    // 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 != "default") {
+            LOGW("Invalid value for touch.orientation.calibration: '%s'",
+                    orientationCalibrationString.string());
+        }
+    }
+}
+
+void TouchInputMapper::resolveCalibration() {
+    // Pressure
+    switch (mCalibration.pressureSource) {
+    case Calibration::PRESSURE_SOURCE_DEFAULT:
+        if (mRawAxes.pressure.valid) {
+            mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE;
+        } else if (mRawAxes.touchMajor.valid) {
+            mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH;
+        }
+        break;
+
+    case Calibration::PRESSURE_SOURCE_PRESSURE:
+        if (! mRawAxes.pressure.valid) {
+            LOGW("Calibration property touch.pressure.source is 'pressure' but "
+                    "the pressure axis is not available.");
+        }
+        break;
+
+    case Calibration::PRESSURE_SOURCE_TOUCH:
+        if (! mRawAxes.touchMajor.valid) {
+            LOGW("Calibration property touch.pressure.source is 'touch' but "
+                    "the touchMajor axis is not available.");
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    switch (mCalibration.pressureCalibration) {
+    case Calibration::PRESSURE_CALIBRATION_DEFAULT:
+        if (mCalibration.pressureSource != Calibration::PRESSURE_SOURCE_DEFAULT) {
+            mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+        } else {
+            mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Tool Size
+    switch (mCalibration.toolSizeCalibration) {
+    case Calibration::TOOL_SIZE_CALIBRATION_DEFAULT:
+        if (mRawAxes.toolMajor.valid) {
+            mCalibration.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_LINEAR;
+        } else {
+            mCalibration.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Touch Size
+    switch (mCalibration.touchSizeCalibration) {
+    case Calibration::TOUCH_SIZE_CALIBRATION_DEFAULT:
+        if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE
+                && mCalibration.toolSizeCalibration != Calibration::TOOL_SIZE_CALIBRATION_NONE) {
+            mCalibration.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE;
+        } else {
+            mCalibration.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Size
+    switch (mCalibration.sizeCalibration) {
+    case Calibration::SIZE_CALIBRATION_DEFAULT:
+        if (mRawAxes.toolMajor.valid) {
+            mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED;
+        } else {
+            mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Orientation
+    switch (mCalibration.orientationCalibration) {
+    case Calibration::ORIENTATION_CALIBRATION_DEFAULT:
+        if (mRawAxes.orientation.valid) {
+            mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+        } else {
+            mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+}
+
+void TouchInputMapper::dumpCalibration(String8& dump) {
+    dump.append(INDENT3 "Calibration:\n");
+
+    // Position
+    if (mCalibration.haveXOrigin) {
+        dump.appendFormat(INDENT4 "touch.position.xOrigin: %d\n", mCalibration.xOrigin);
+    }
+    if (mCalibration.haveYOrigin) {
+        dump.appendFormat(INDENT4 "touch.position.yOrigin: %d\n", mCalibration.yOrigin);
+    }
+    if (mCalibration.haveXScale) {
+        dump.appendFormat(INDENT4 "touch.position.xScale: %0.3f\n", mCalibration.xScale);
+    }
+    if (mCalibration.haveYScale) {
+        dump.appendFormat(INDENT4 "touch.position.yScale: %0.3f\n", mCalibration.yScale);
+    }
+
+    // Touch Size
+    switch (mCalibration.touchSizeCalibration) {
+    case Calibration::TOUCH_SIZE_CALIBRATION_NONE:
+        dump.append(INDENT4 "touch.touchSize.calibration: none\n");
+        break;
+    case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC:
+        dump.append(INDENT4 "touch.touchSize.calibration: geometric\n");
+        break;
+    case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE:
+        dump.append(INDENT4 "touch.touchSize.calibration: pressure\n");
+        break;
+    default:
+        assert(false);
+    }
+
+    // Tool Size
+    switch (mCalibration.toolSizeCalibration) {
+    case Calibration::TOOL_SIZE_CALIBRATION_NONE:
+        dump.append(INDENT4 "touch.toolSize.calibration: none\n");
+        break;
+    case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC:
+        dump.append(INDENT4 "touch.toolSize.calibration: geometric\n");
+        break;
+    case Calibration::TOOL_SIZE_CALIBRATION_LINEAR:
+        dump.append(INDENT4 "touch.toolSize.calibration: linear\n");
+        break;
+    case Calibration::TOOL_SIZE_CALIBRATION_AREA:
+        dump.append(INDENT4 "touch.toolSize.calibration: area\n");
+        break;
+    default:
+        assert(false);
+    }
+
+    if (mCalibration.haveToolSizeLinearScale) {
+        dump.appendFormat(INDENT4 "touch.toolSize.linearScale: %0.3f\n",
+                mCalibration.toolSizeLinearScale);
+    }
+
+    if (mCalibration.haveToolSizeLinearBias) {
+        dump.appendFormat(INDENT4 "touch.toolSize.linearBias: %0.3f\n",
+                mCalibration.toolSizeLinearBias);
+    }
+
+    if (mCalibration.haveToolSizeAreaScale) {
+        dump.appendFormat(INDENT4 "touch.toolSize.areaScale: %0.3f\n",
+                mCalibration.toolSizeAreaScale);
+    }
+
+    if (mCalibration.haveToolSizeAreaBias) {
+        dump.appendFormat(INDENT4 "touch.toolSize.areaBias: %0.3f\n",
+                mCalibration.toolSizeAreaBias);
+    }
+
+    if (mCalibration.haveToolSizeIsSummed) {
+        dump.appendFormat(INDENT4 "touch.toolSize.isSummed: %s\n",
+                toString(mCalibration.toolSizeIsSummed));
+    }
+
+    // Pressure
+    switch (mCalibration.pressureCalibration) {
+    case Calibration::PRESSURE_CALIBRATION_NONE:
+        dump.append(INDENT4 "touch.pressure.calibration: none\n");
+        break;
+    case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+        dump.append(INDENT4 "touch.pressure.calibration: physical\n");
+        break;
+    case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+        dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
+        break;
+    default:
+        assert(false);
+    }
+
+    switch (mCalibration.pressureSource) {
+    case Calibration::PRESSURE_SOURCE_PRESSURE:
+        dump.append(INDENT4 "touch.pressure.source: pressure\n");
+        break;
+    case Calibration::PRESSURE_SOURCE_TOUCH:
+        dump.append(INDENT4 "touch.pressure.source: touch\n");
+        break;
+    case Calibration::PRESSURE_SOURCE_DEFAULT:
+        break;
+    default:
+        assert(false);
+    }
+
+    if (mCalibration.havePressureScale) {
+        dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+                mCalibration.pressureScale);
+    }
+
+    // Size
+    switch (mCalibration.sizeCalibration) {
+    case Calibration::SIZE_CALIBRATION_NONE:
+        dump.append(INDENT4 "touch.size.calibration: none\n");
+        break;
+    case Calibration::SIZE_CALIBRATION_NORMALIZED:
+        dump.append(INDENT4 "touch.size.calibration: normalized\n");
+        break;
+    default:
+        assert(false);
+    }
+
+    // Orientation
+    switch (mCalibration.orientationCalibration) {
+    case Calibration::ORIENTATION_CALIBRATION_NONE:
+        dump.append(INDENT4 "touch.orientation.calibration: none\n");
+        break;
+    case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+        dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
+        break;
+    default:
+        assert(false);
+    }
+}
+
+void TouchInputMapper::reset() {
+    // Synthesize touch up event if touch is currently down.
+    // This will also take care of finishing virtual key processing if needed.
+    if (mLastTouch.pointerCount != 0) {
+        nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+        mCurrentTouch.clear();
+        syncTouch(when, true);
+    }
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+        initializeLocked();
+    } // release lock
+
+    InputMapper::reset();
+}
+
+void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
+    uint32_t policyFlags = 0;
+
+    // Preprocess pointer data.
+
+    if (mParameters.useBadTouchFilter) {
+        if (applyBadTouchFilter()) {
+            havePointerIds = false;
+        }
+    }
+
+    if (mParameters.useJumpyTouchFilter) {
+        if (applyJumpyTouchFilter()) {
+            havePointerIds = false;
+        }
+    }
+
+    if (! havePointerIds) {
+        calculatePointerIds();
+    }
+
+    TouchData temp;
+    TouchData* savedTouch;
+    if (mParameters.useAveragingTouchFilter) {
+        temp.copyFrom(mCurrentTouch);
+        savedTouch = & temp;
+
+        applyAveragingTouchFilter();
+    } else {
+        savedTouch = & mCurrentTouch;
+    }
+
+    // Process touches and virtual keys.
+
+    TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
+    if (touchResult == DISPATCH_TOUCH) {
+        dispatchTouches(when, policyFlags);
+    }
+
+    // Copy current touch to last touch in preparation for the next cycle.
+
+    if (touchResult == DROP_STROKE) {
+        mLastTouch.clear();
+    } else {
+        mLastTouch.copyFrom(*savedTouch);
+    }
+}
+
+TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
+        nsecs_t when, uint32_t policyFlags) {
+    int32_t keyEventAction, keyEventFlags;
+    int32_t keyCode, scanCode, downTime;
+    TouchResult touchResult;
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        // Update surface size and orientation, including virtual key positions.
+        if (! configureSurfaceLocked()) {
+            return DROP_STROKE;
+        }
+
+        // Check for virtual key press.
+        if (mLocked.currentVirtualKey.down) {
+            if (mCurrentTouch.pointerCount == 0) {
+                // Pointer went up while virtual key was down.
+                mLocked.currentVirtualKey.down = false;
+#if DEBUG_VIRTUAL_KEYS
+                LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+                        mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
+#endif
+                keyEventAction = AKEY_EVENT_ACTION_UP;
+                keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+                touchResult = SKIP_TOUCH;
+                goto DispatchVirtualKey;
+            }
+
+            if (mCurrentTouch.pointerCount == 1) {
+                int32_t x = mCurrentTouch.pointers[0].x;
+                int32_t y = mCurrentTouch.pointers[0].y;
+                const VirtualKey* virtualKey = findVirtualKeyHitLocked(x, y);
+                if (virtualKey && virtualKey->keyCode == mLocked.currentVirtualKey.keyCode) {
+                    // Pointer is still within the space of the virtual key.
+                    return SKIP_TOUCH;
+                }
+            }
+
+            // Pointer left virtual key area or another pointer also went down.
+            // Send key cancellation and drop the stroke so subsequent motions will be
+            // considered fresh downs.  This is useful when the user swipes away from the
+            // virtual key area into the main display surface.
+            mLocked.currentVirtualKey.down = false;
+#if DEBUG_VIRTUAL_KEYS
+            LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+                    mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
+#endif
+            keyEventAction = AKEY_EVENT_ACTION_UP;
+            keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+                    | AKEY_EVENT_FLAG_CANCELED;
+
+            // Check whether the pointer moved inside the display area where we should
+            // start a new stroke.
+            int32_t x = mCurrentTouch.pointers[0].x;
+            int32_t y = mCurrentTouch.pointers[0].y;
+            if (isPointInsideSurfaceLocked(x, y)) {
+                mLastTouch.clear();
+                touchResult = DISPATCH_TOUCH;
+            } else {
+                touchResult = DROP_STROKE;
+            }
+        } else {
+            if (mCurrentTouch.pointerCount >= 1 && mLastTouch.pointerCount == 0) {
+                // Pointer just went down.  Handle off-screen touches, if needed.
+                int32_t x = mCurrentTouch.pointers[0].x;
+                int32_t y = mCurrentTouch.pointers[0].y;
+                if (! isPointInsideSurfaceLocked(x, y)) {
+                    // If exactly one pointer went down, check for virtual key hit.
+                    // Otherwise we will drop the entire stroke.
+                    if (mCurrentTouch.pointerCount == 1) {
+                        const VirtualKey* virtualKey = findVirtualKeyHitLocked(x, y);
+                        if (virtualKey) {
+                            mLocked.currentVirtualKey.down = true;
+                            mLocked.currentVirtualKey.downTime = when;
+                            mLocked.currentVirtualKey.keyCode = virtualKey->keyCode;
+                            mLocked.currentVirtualKey.scanCode = virtualKey->scanCode;
+#if DEBUG_VIRTUAL_KEYS
+                            LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+                                    mLocked.currentVirtualKey.keyCode,
+                                    mLocked.currentVirtualKey.scanCode);
+#endif
+                            keyEventAction = AKEY_EVENT_ACTION_DOWN;
+                            keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM
+                                    | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+                            touchResult = SKIP_TOUCH;
+                            goto DispatchVirtualKey;
+                        }
+                    }
+                    return DROP_STROKE;
+                }
+            }
+            return DISPATCH_TOUCH;
+        }
+
+    DispatchVirtualKey:
+        // Collect remaining state needed to dispatch virtual key.
+        keyCode = mLocked.currentVirtualKey.keyCode;
+        scanCode = mLocked.currentVirtualKey.scanCode;
+        downTime = mLocked.currentVirtualKey.downTime;
+    } // release lock
+
+    // Dispatch virtual key.
+    int32_t metaState = mContext->getGlobalMetaState();
+    policyFlags |= POLICY_FLAG_VIRTUAL;
+    getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
+            keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+    return touchResult;
+}
+
+void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+    uint32_t currentPointerCount = mCurrentTouch.pointerCount;
+    uint32_t lastPointerCount = mLastTouch.pointerCount;
+    if (currentPointerCount == 0 && lastPointerCount == 0) {
+        return; // nothing to do!
+    }
+
+    BitSet32 currentIdBits = mCurrentTouch.idBits;
+    BitSet32 lastIdBits = mLastTouch.idBits;
+
+    if (currentIdBits == lastIdBits) {
+        // No pointer id changes so this is a move event.
+        // The dispatcher takes care of batching moves so we don't have to deal with that here.
+        int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+        dispatchTouch(when, policyFlags, & mCurrentTouch,
+                currentIdBits, -1, currentPointerCount, motionEventAction);
+    } 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 activeIdBits(lastIdBits.value);
+        uint32_t pointerCount = lastPointerCount;
+
+        // Produce an intermediate representation of the touch data that consists of the
+        // old location of pointers that have just gone up and the new location of pointers that
+        // have just moved but omits the location of pointers that have just gone down.
+        TouchData interimTouch;
+        interimTouch.copyFrom(mLastTouch);
+
+        BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
+        bool moveNeeded = false;
+        while (!moveIdBits.isEmpty()) {
+            uint32_t moveId = moveIdBits.firstMarkedBit();
+            moveIdBits.clearBit(moveId);
+
+            int32_t oldIndex = mLastTouch.idToIndex[moveId];
+            int32_t newIndex = mCurrentTouch.idToIndex[moveId];
+            if (mLastTouch.pointers[oldIndex] != mCurrentTouch.pointers[newIndex]) {
+                interimTouch.pointers[oldIndex] = mCurrentTouch.pointers[newIndex];
+                moveNeeded = true;
+            }
+        }
+
+        // Dispatch pointer up events using the interim pointer locations.
+        while (!upIdBits.isEmpty()) {
+            uint32_t upId = upIdBits.firstMarkedBit();
+            upIdBits.clearBit(upId);
+            BitSet32 oldActiveIdBits = activeIdBits;
+            activeIdBits.clearBit(upId);
+
+            int32_t motionEventAction;
+            if (activeIdBits.isEmpty()) {
+                motionEventAction = AMOTION_EVENT_ACTION_UP;
+            } else {
+                motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP;
+            }
+
+            dispatchTouch(when, policyFlags, &interimTouch,
+                    oldActiveIdBits, upId, pointerCount, motionEventAction);
+            pointerCount -= 1;
+        }
+
+        // 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) {
+            dispatchTouch(when, policyFlags, &mCurrentTouch,
+                    activeIdBits, -1, pointerCount, AMOTION_EVENT_ACTION_MOVE);
+        }
+
+        // Dispatch pointer down events using the new pointer locations.
+        while (!downIdBits.isEmpty()) {
+            uint32_t downId = downIdBits.firstMarkedBit();
+            downIdBits.clearBit(downId);
+            BitSet32 oldActiveIdBits = activeIdBits;
+            activeIdBits.markBit(downId);
+
+            int32_t motionEventAction;
+            if (oldActiveIdBits.isEmpty()) {
+                motionEventAction = AMOTION_EVENT_ACTION_DOWN;
+                mDownTime = when;
+            } else {
+                motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN;
+            }
+
+            pointerCount += 1;
+            dispatchTouch(when, policyFlags, &mCurrentTouch,
+                    activeIdBits, downId, pointerCount, motionEventAction);
+        }
+    }
+}
+
+void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags,
+        TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
+        int32_t motionEventAction) {
+    int32_t pointerIds[MAX_POINTERS];
+    PointerCoords pointerCoords[MAX_POINTERS];
+    int32_t motionEventEdgeFlags = 0;
+    float xPrecision, yPrecision;
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        // Walk through the the active pointers and map touch screen coordinates (TouchData) into
+        // display coordinates (PointerCoords) and adjust for display orientation.
+        for (uint32_t outIndex = 0; ! idBits.isEmpty(); outIndex++) {
+            uint32_t id = idBits.firstMarkedBit();
+            idBits.clearBit(id);
+            uint32_t inIndex = touch->idToIndex[id];
+
+            const PointerData& in = touch->pointers[inIndex];
+
+            // X and Y
+            float x = float(in.x - mLocked.xOrigin) * mLocked.xScale;
+            float y = float(in.y - mLocked.yOrigin) * mLocked.yScale;
+
+            // ToolMajor and ToolMinor
+            float toolMajor, toolMinor;
+            switch (mCalibration.toolSizeCalibration) {
+            case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC:
+                toolMajor = in.toolMajor * mLocked.geometricScale;
+                if (mRawAxes.toolMinor.valid) {
+                    toolMinor = in.toolMinor * mLocked.geometricScale;
+                } else {
+                    toolMinor = toolMajor;
+                }
+                break;
+            case Calibration::TOOL_SIZE_CALIBRATION_LINEAR:
+                toolMajor = in.toolMajor != 0
+                        ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias
+                        : 0;
+                if (mRawAxes.toolMinor.valid) {
+                    toolMinor = in.toolMinor != 0
+                            ? in.toolMinor * mLocked.toolSizeLinearScale
+                                    + mLocked.toolSizeLinearBias
+                            : 0;
+                } else {
+                    toolMinor = toolMajor;
+                }
+                break;
+            case Calibration::TOOL_SIZE_CALIBRATION_AREA:
+                if (in.toolMajor != 0) {
+                    float diameter = sqrtf(in.toolMajor
+                            * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias);
+                    toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias;
+                } else {
+                    toolMajor = 0;
+                }
+                toolMinor = toolMajor;
+                break;
+            default:
+                toolMajor = 0;
+                toolMinor = 0;
+                break;
+            }
+
+            if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) {
+                toolMajor /= pointerCount;
+                toolMinor /= pointerCount;
+            }
+
+            // Pressure
+            float rawPressure;
+            switch (mCalibration.pressureSource) {
+            case Calibration::PRESSURE_SOURCE_PRESSURE:
+                rawPressure = in.pressure;
+                break;
+            case Calibration::PRESSURE_SOURCE_TOUCH:
+                rawPressure = in.touchMajor;
+                break;
+            default:
+                rawPressure = 0;
+            }
+
+            float pressure;
+            switch (mCalibration.pressureCalibration) {
+            case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+            case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+                pressure = rawPressure * mLocked.pressureScale;
+                break;
+            default:
+                pressure = 1;
+                break;
+            }
+
+            // TouchMajor and TouchMinor
+            float touchMajor, touchMinor;
+            switch (mCalibration.touchSizeCalibration) {
+            case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC:
+                touchMajor = in.touchMajor * mLocked.geometricScale;
+                if (mRawAxes.touchMinor.valid) {
+                    touchMinor = in.touchMinor * mLocked.geometricScale;
+                } else {
+                    touchMinor = touchMajor;
+                }
+                break;
+            case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE:
+                touchMajor = toolMajor * pressure;
+                touchMinor = toolMinor * pressure;
+                break;
+            default:
+                touchMajor = 0;
+                touchMinor = 0;
+                break;
+            }
+
+            if (touchMajor > toolMajor) {
+                touchMajor = toolMajor;
+            }
+            if (touchMinor > toolMinor) {
+                touchMinor = toolMinor;
+            }
+
+            // Size
+            float size;
+            switch (mCalibration.sizeCalibration) {
+            case Calibration::SIZE_CALIBRATION_NORMALIZED: {
+                float rawSize = mRawAxes.toolMinor.valid
+                        ? avg(in.toolMajor, in.toolMinor)
+                        : in.toolMajor;
+                size = rawSize * mLocked.sizeScale;
+                break;
+            }
+            default:
+                size = 0;
+                break;
+            }
+
+            // Orientation
+            float orientation;
+            switch (mCalibration.orientationCalibration) {
+            case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+                orientation = in.orientation * mLocked.orientationScale;
+                break;
+            default:
+                orientation = 0;
+            }
+
+            // Adjust coords for orientation.
+            switch (mLocked.surfaceOrientation) {
+            case DISPLAY_ORIENTATION_90: {
+                float xTemp = x;
+                x = y;
+                y = mLocked.surfaceWidth - xTemp;
+                orientation -= M_PI_2;
+                if (orientation < - M_PI_2) {
+                    orientation += M_PI;
+                }
+                break;
+            }
+            case DISPLAY_ORIENTATION_180: {
+                x = mLocked.surfaceWidth - x;
+                y = mLocked.surfaceHeight - y;
+                orientation = - orientation;
+                break;
+            }
+            case DISPLAY_ORIENTATION_270: {
+                float xTemp = x;
+                x = mLocked.surfaceHeight - y;
+                y = xTemp;
+                orientation += M_PI_2;
+                if (orientation > M_PI_2) {
+                    orientation -= M_PI;
+                }
+                break;
+            }
+            }
+
+            // Write output coords.
+            PointerCoords& out = pointerCoords[outIndex];
+            out.x = x;
+            out.y = y;
+            out.pressure = pressure;
+            out.size = size;
+            out.touchMajor = touchMajor;
+            out.touchMinor = touchMinor;
+            out.toolMajor = toolMajor;
+            out.toolMinor = toolMinor;
+            out.orientation = orientation;
+
+            pointerIds[outIndex] = int32_t(id);
+
+            if (id == changedId) {
+                motionEventAction |= outIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+            }
+        }
+
+        // Check edge flags by looking only at the first pointer since the flags are
+        // global to the event.
+        if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) {
+            if (pointerCoords[0].x <= 0) {
+                motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_LEFT;
+            } else if (pointerCoords[0].x >= mLocked.orientedSurfaceWidth) {
+                motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_RIGHT;
+            }
+            if (pointerCoords[0].y <= 0) {
+                motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_TOP;
+            } else if (pointerCoords[0].y >= mLocked.orientedSurfaceHeight) {
+                motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_BOTTOM;
+            }
+        }
+
+        xPrecision = mLocked.orientedXPrecision;
+        yPrecision = mLocked.orientedYPrecision;
+    } // release lock
+
+    getDispatcher()->notifyMotion(when, getDeviceId(), mSources, policyFlags,
+            motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
+            pointerCount, pointerIds, pointerCoords,
+            xPrecision, yPrecision, mDownTime);
+}
+
+bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) {
+    if (mRawAxes.x.valid && mRawAxes.y.valid) {
+        return x >= mRawAxes.x.minValue && x <= mRawAxes.x.maxValue
+                && y >= mRawAxes.y.minValue && y <= mRawAxes.y.maxValue;
+    }
+    return true;
+}
+
+const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHitLocked(
+        int32_t x, int32_t y) {
+    size_t numVirtualKeys = mLocked.virtualKeys.size();
+    for (size_t i = 0; i < numVirtualKeys; i++) {
+        const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+
+#if DEBUG_VIRTUAL_KEYS
+        LOGD("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 NULL;
+}
+
+void TouchInputMapper::calculatePointerIds() {
+    uint32_t currentPointerCount = mCurrentTouch.pointerCount;
+    uint32_t lastPointerCount = mLastTouch.pointerCount;
+
+    if (currentPointerCount == 0) {
+        // No pointers to assign.
+        mCurrentTouch.idBits.clear();
+    } else if (lastPointerCount == 0) {
+        // All pointers are new.
+        mCurrentTouch.idBits.clear();
+        for (uint32_t i = 0; i < currentPointerCount; i++) {
+            mCurrentTouch.pointers[i].id = i;
+            mCurrentTouch.idToIndex[i] = i;
+            mCurrentTouch.idBits.markBit(i);
+        }
+    } else if (currentPointerCount == 1 && lastPointerCount == 1) {
+        // Only one pointer and no change in count so it must have the same id as before.
+        uint32_t id = mLastTouch.pointers[0].id;
+        mCurrentTouch.pointers[0].id = id;
+        mCurrentTouch.idToIndex[id] = 0;
+        mCurrentTouch.idBits.value = BitSet32::valueForBit(id);
+    } else {
+        // 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.
+        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++) {
+                int64_t deltaX = mCurrentTouch.pointers[currentPointerIndex].x
+                        - mLastTouch.pointers[lastPointerIndex].x;
+                int64_t deltaY = mCurrentTouch.pointers[currentPointerIndex].y
+                        - mLastTouch.pointers[lastPointerIndex].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
+        LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize);
+        for (size_t i = 0; i < heapSize; i++) {
+            LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
+                    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); i > 0; i--) {
+            for (;;) {
+                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).
+                    heapSize -= 1;
+                    assert(heapSize > 0);
+
+                    // 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
+                    LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize);
+                    for (size_t i = 0; i < heapSize; i++) {
+                        LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
+                                i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+                                heap[i].distance);
+                    }
+#endif
+                }
+
+                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 = mLastTouch.pointers[lastPointerIndex].id;
+                mCurrentTouch.pointers[currentPointerIndex].id = id;
+                mCurrentTouch.idToIndex[id] = currentPointerIndex;
+                usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+                LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
+                        lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
+                break;
+            }
+        }
+
+        // Assign fresh ids to new pointers.
+        if (currentPointerCount > lastPointerCount) {
+            for (uint32_t i = currentPointerCount - lastPointerCount; ;) {
+                uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit();
+                uint32_t id = usedIdBits.firstUnmarkedBit();
+
+                mCurrentTouch.pointers[currentPointerIndex].id = id;
+                mCurrentTouch.idToIndex[id] = currentPointerIndex;
+                usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+                LOGD("calculatePointerIds - assigned: cur=%d, id=%d",
+                        currentPointerIndex, id);
+#endif
+
+                if (--i == 0) break; // done
+                matchedCurrentBits.markBit(currentPointerIndex);
+            }
+        }
+
+        // Fix id bits.
+        mCurrentTouch.idBits = usedIdBits;
+    }
+}
+
+/* Special hack for devices that have bad screen data: if one of the
+ * points has moved more than a screen height from the last position,
+ * then drop it. */
+bool TouchInputMapper::applyBadTouchFilter() {
+    // This hack requires valid axis parameters.
+    if (! mRawAxes.y.valid) {
+        return false;
+    }
+
+    uint32_t pointerCount = mCurrentTouch.pointerCount;
+
+    // Nothing to do if there are no points.
+    if (pointerCount == 0) {
+        return false;
+    }
+
+    // Don't do anything if a finger is going down or up.  We run
+    // here before assigning pointer IDs, so there isn't a good
+    // way to do per-finger matching.
+    if (pointerCount != mLastTouch.pointerCount) {
+        return false;
+    }
+
+    // We consider a single movement across more than a 7/16 of
+    // the long size of the screen to be bad.  This was a magic value
+    // determined by looking at the maximum distance it is feasible
+    // to actually move in one sample.
+    int32_t maxDeltaY = mRawAxes.y.getRange() * 7 / 16;
+
+    // XXX The original code in InputDevice.java included commented out
+    //     code for testing the X axis.  Note that when we drop a point
+    //     we don't actually restore the old X either.  Strange.
+    //     The old code also tries to track when bad points were previously
+    //     detected but it turns out that due to the placement of a "break"
+    //     at the end of the loop, we never set mDroppedBadPoint to true
+    //     so it is effectively dead code.
+    // Need to figure out if the old code is busted or just overcomplicated
+    // but working as intended.
+
+    // Look through all new points and see if any are farther than
+    // acceptable from all previous points.
+    for (uint32_t i = pointerCount; i-- > 0; ) {
+        int32_t y = mCurrentTouch.pointers[i].y;
+        int32_t closestY = INT_MAX;
+        int32_t closestDeltaY = 0;
+
+#if DEBUG_HACKS
+        LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y);
+#endif
+
+        for (uint32_t j = pointerCount; j-- > 0; ) {
+            int32_t lastY = mLastTouch.pointers[j].y;
+            int32_t deltaY = abs(y - lastY);
+
+#if DEBUG_HACKS
+            LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d",
+                    j, lastY, deltaY);
+#endif
+
+            if (deltaY < maxDeltaY) {
+                goto SkipSufficientlyClosePoint;
+            }
+            if (deltaY < closestDeltaY) {
+                closestDeltaY = deltaY;
+                closestY = lastY;
+            }
+        }
+
+        // Must not have found a close enough match.
+#if DEBUG_HACKS
+        LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d",
+                i, y, closestY, closestDeltaY, maxDeltaY);
+#endif
+
+        mCurrentTouch.pointers[i].y = closestY;
+        return true; // XXX original code only corrects one point
+
+    SkipSufficientlyClosePoint: ;
+    }
+
+    // No change.
+    return false;
+}
+
+/* Special hack for devices that have bad screen data: drop points where
+ * the coordinate value for one axis has jumped to the other pointer's location.
+ */
+bool TouchInputMapper::applyJumpyTouchFilter() {
+    // This hack requires valid axis parameters.
+    if (! mRawAxes.y.valid) {
+        return false;
+    }
+
+    uint32_t pointerCount = mCurrentTouch.pointerCount;
+    if (mLastTouch.pointerCount != pointerCount) {
+#if DEBUG_HACKS
+        LOGD("JumpyTouchFilter: Different pointer count %d -> %d",
+                mLastTouch.pointerCount, pointerCount);
+        for (uint32_t i = 0; i < pointerCount; i++) {
+            LOGD("  Pointer %d (%d, %d)", i,
+                    mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y);
+        }
+#endif
+
+        if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
+            if (mLastTouch.pointerCount == 1 && pointerCount == 2) {
+                // Just drop the first few events going from 1 to 2 pointers.
+                // They're bad often enough that they're not worth considering.
+                mCurrentTouch.pointerCount = 1;
+                mJumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+                LOGD("JumpyTouchFilter: Pointer 2 dropped");
+#endif
+                return true;
+            } else if (mLastTouch.pointerCount == 2 && pointerCount == 1) {
+                // The event when we go from 2 -> 1 tends to be messed up too
+                mCurrentTouch.pointerCount = 2;
+                mCurrentTouch.pointers[0] = mLastTouch.pointers[0];
+                mCurrentTouch.pointers[1] = mLastTouch.pointers[1];
+                mJumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+                for (int32_t i = 0; i < 2; i++) {
+                    LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i,
+                            mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y);
+                }
+#endif
+                return true;
+            }
+        }
+        // Reset jumpy points dropped on other transitions or if limit exceeded.
+        mJumpyTouchFilter.jumpyPointsDropped = 0;
+
+#if DEBUG_HACKS
+        LOGD("JumpyTouchFilter: Transition - drop limit reset");
+#endif
+        return false;
+    }
+
+    // We have the same number of pointers as last time.
+    // A 'jumpy' point is one where the coordinate value for one axis
+    // has jumped to the other pointer's location. No need to do anything
+    // else if we only have one pointer.
+    if (pointerCount < 2) {
+        return false;
+    }
+
+    if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) {
+        int jumpyEpsilon = mRawAxes.y.getRange() / JUMPY_EPSILON_DIVISOR;
+
+        // We only replace the single worst jumpy point as characterized by pointer distance
+        // in a single axis.
+        int32_t badPointerIndex = -1;
+        int32_t badPointerReplacementIndex = -1;
+        int32_t badPointerDistance = INT_MIN; // distance to be corrected
+
+        for (uint32_t i = pointerCount; i-- > 0; ) {
+            int32_t x = mCurrentTouch.pointers[i].x;
+            int32_t y = mCurrentTouch.pointers[i].y;
+
+#if DEBUG_HACKS
+            LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y);
+#endif
+
+            // Check if a touch point is too close to another's coordinates
+            bool dropX = false, dropY = false;
+            for (uint32_t j = 0; j < pointerCount; j++) {
+                if (i == j) {
+                    continue;
+                }
+
+                if (abs(x - mCurrentTouch.pointers[j].x) <= jumpyEpsilon) {
+                    dropX = true;
+                    break;
+                }
+
+                if (abs(y - mCurrentTouch.pointers[j].y) <= jumpyEpsilon) {
+                    dropY = true;
+                    break;
+                }
+            }
+            if (! dropX && ! dropY) {
+                continue; // not jumpy
+            }
+
+            // Find a replacement candidate by comparing with older points on the
+            // complementary (non-jumpy) axis.
+            int32_t distance = INT_MIN; // distance to be corrected
+            int32_t replacementIndex = -1;
+
+            if (dropX) {
+                // X looks too close.  Find an older replacement point with a close Y.
+                int32_t smallestDeltaY = INT_MAX;
+                for (uint32_t j = 0; j < pointerCount; j++) {
+                    int32_t deltaY = abs(y - mLastTouch.pointers[j].y);
+                    if (deltaY < smallestDeltaY) {
+                        smallestDeltaY = deltaY;
+                        replacementIndex = j;
+                    }
+                }
+                distance = abs(x - mLastTouch.pointers[replacementIndex].x);
+            } else {
+                // Y looks too close.  Find an older replacement point with a close X.
+                int32_t smallestDeltaX = INT_MAX;
+                for (uint32_t j = 0; j < pointerCount; j++) {
+                    int32_t deltaX = abs(x - mLastTouch.pointers[j].x);
+                    if (deltaX < smallestDeltaX) {
+                        smallestDeltaX = deltaX;
+                        replacementIndex = j;
+                    }
+                }
+                distance = abs(y - mLastTouch.pointers[replacementIndex].y);
+            }
+
+            // If replacing this pointer would correct a worse error than the previous ones
+            // considered, then use this replacement instead.
+            if (distance > badPointerDistance) {
+                badPointerIndex = i;
+                badPointerReplacementIndex = replacementIndex;
+                badPointerDistance = distance;
+            }
+        }
+
+        // Correct the jumpy pointer if one was found.
+        if (badPointerIndex >= 0) {
+#if DEBUG_HACKS
+            LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)",
+                    badPointerIndex,
+                    mLastTouch.pointers[badPointerReplacementIndex].x,
+                    mLastTouch.pointers[badPointerReplacementIndex].y);
+#endif
+
+            mCurrentTouch.pointers[badPointerIndex].x =
+                    mLastTouch.pointers[badPointerReplacementIndex].x;
+            mCurrentTouch.pointers[badPointerIndex].y =
+                    mLastTouch.pointers[badPointerReplacementIndex].y;
+            mJumpyTouchFilter.jumpyPointsDropped += 1;
+            return true;
+        }
+    }
+
+    mJumpyTouchFilter.jumpyPointsDropped = 0;
+    return false;
+}
+
+/* Special hack for devices that have bad screen data: aggregate and
+ * compute averages of the coordinate data, to reduce the amount of
+ * jitter seen by applications. */
+void TouchInputMapper::applyAveragingTouchFilter() {
+    for (uint32_t currentIndex = 0; currentIndex < mCurrentTouch.pointerCount; currentIndex++) {
+        uint32_t id = mCurrentTouch.pointers[currentIndex].id;
+        int32_t x = mCurrentTouch.pointers[currentIndex].x;
+        int32_t y = mCurrentTouch.pointers[currentIndex].y;
+        int32_t pressure;
+        switch (mCalibration.pressureSource) {
+        case Calibration::PRESSURE_SOURCE_PRESSURE:
+            pressure = mCurrentTouch.pointers[currentIndex].pressure;
+            break;
+        case Calibration::PRESSURE_SOURCE_TOUCH:
+            pressure = mCurrentTouch.pointers[currentIndex].touchMajor;
+            break;
+        default:
+            pressure = 1;
+            break;
+        }
+
+        if (mLastTouch.idBits.hasBit(id)) {
+            // Pointer was down before and is still down now.
+            // Compute average over history trace.
+            uint32_t start = mAveragingTouchFilter.historyStart[id];
+            uint32_t end = mAveragingTouchFilter.historyEnd[id];
+
+            int64_t deltaX = x - mAveragingTouchFilter.historyData[end].pointers[id].x;
+            int64_t deltaY = y - mAveragingTouchFilter.historyData[end].pointers[id].y;
+            uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+#if DEBUG_HACKS
+            LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld",
+                    id, distance);
+#endif
+
+            if (distance < AVERAGING_DISTANCE_LIMIT) {
+                // Increment end index in preparation for recording new historical data.
+                end += 1;
+                if (end > AVERAGING_HISTORY_SIZE) {
+                    end = 0;
+                }
+
+                // If the end index has looped back to the start index then we have filled
+                // the historical trace up to the desired size so we drop the historical
+                // data at the start of the trace.
+                if (end == start) {
+                    start += 1;
+                    if (start > AVERAGING_HISTORY_SIZE) {
+                        start = 0;
+                    }
+                }
+
+                // Add the raw data to the historical trace.
+                mAveragingTouchFilter.historyStart[id] = start;
+                mAveragingTouchFilter.historyEnd[id] = end;
+                mAveragingTouchFilter.historyData[end].pointers[id].x = x;
+                mAveragingTouchFilter.historyData[end].pointers[id].y = y;
+                mAveragingTouchFilter.historyData[end].pointers[id].pressure = pressure;
+
+                // Average over all historical positions in the trace by total pressure.
+                int32_t averagedX = 0;
+                int32_t averagedY = 0;
+                int32_t totalPressure = 0;
+                for (;;) {
+                    int32_t historicalX = mAveragingTouchFilter.historyData[start].pointers[id].x;
+                    int32_t historicalY = mAveragingTouchFilter.historyData[start].pointers[id].y;
+                    int32_t historicalPressure = mAveragingTouchFilter.historyData[start]
+                            .pointers[id].pressure;
+
+                    averagedX += historicalX * historicalPressure;
+                    averagedY += historicalY * historicalPressure;
+                    totalPressure += historicalPressure;
+
+                    if (start == end) {
+                        break;
+                    }
+
+                    start += 1;
+                    if (start > AVERAGING_HISTORY_SIZE) {
+                        start = 0;
+                    }
+                }
+
+                if (totalPressure != 0) {
+                    averagedX /= totalPressure;
+                    averagedY /= totalPressure;
+
+#if DEBUG_HACKS
+                    LOGD("AveragingTouchFilter: Pointer id %d - "
+                            "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
+                            averagedX, averagedY);
+#endif
+
+                    mCurrentTouch.pointers[currentIndex].x = averagedX;
+                    mCurrentTouch.pointers[currentIndex].y = averagedY;
+                }
+            } else {
+#if DEBUG_HACKS
+                LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id);
+#endif
+            }
+        } else {
+#if DEBUG_HACKS
+            LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id);
+#endif
+        }
+
+        // Reset pointer history.
+        mAveragingTouchFilter.historyStart[id] = 0;
+        mAveragingTouchFilter.historyEnd[id] = 0;
+        mAveragingTouchFilter.historyData[0].pointers[id].x = x;
+        mAveragingTouchFilter.historyData[0].pointers[id].y = y;
+        mAveragingTouchFilter.historyData[0].pointers[id].pressure = pressure;
+    }
+}
+
+int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.currentVirtualKey.down && mLocked.currentVirtualKey.keyCode == keyCode) {
+            return AKEY_STATE_VIRTUAL;
+        }
+
+        size_t numVirtualKeys = mLocked.virtualKeys.size();
+        for (size_t i = 0; i < numVirtualKeys; i++) {
+            const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+            if (virtualKey.keyCode == keyCode) {
+                return AKEY_STATE_UP;
+            }
+        }
+    } // release lock
+
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.currentVirtualKey.down && mLocked.currentVirtualKey.scanCode == scanCode) {
+            return AKEY_STATE_VIRTUAL;
+        }
+
+        size_t numVirtualKeys = mLocked.virtualKeys.size();
+        for (size_t i = 0; i < numVirtualKeys; i++) {
+            const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+            if (virtualKey.scanCode == scanCode) {
+                return AKEY_STATE_UP;
+            }
+        }
+    } // release lock
+
+    return AKEY_STATE_UNKNOWN;
+}
+
+bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        size_t numVirtualKeys = mLocked.virtualKeys.size();
+        for (size_t i = 0; i < numVirtualKeys; i++) {
+            const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+
+            for (size_t i = 0; i < numCodes; i++) {
+                if (virtualKey.keyCode == keyCodes[i]) {
+                    outFlags[i] = 1;
+                }
+            }
+        }
+    } // release lock
+
+    return true;
+}
+
+
+// --- SingleTouchInputMapper ---
+
+SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) :
+        TouchInputMapper(device) {
+    initialize();
+}
+
+SingleTouchInputMapper::~SingleTouchInputMapper() {
+}
+
+void SingleTouchInputMapper::initialize() {
+    mAccumulator.clear();
+
+    mDown = false;
+    mX = 0;
+    mY = 0;
+    mPressure = 0; // default to 0 for devices that don't report pressure
+    mToolWidth = 0; // default to 0 for devices that don't report tool width
+}
+
+void SingleTouchInputMapper::reset() {
+    TouchInputMapper::reset();
+
+    initialize();
+ }
+
+void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_KEY:
+        switch (rawEvent->scanCode) {
+        case BTN_TOUCH:
+            mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH;
+            mAccumulator.btnTouch = rawEvent->value != 0;
+            // Don't sync immediately.  Wait until the next SYN_REPORT since we might
+            // not have received valid position information yet.  This logic assumes that
+            // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet.
+            break;
+        }
+        break;
+
+    case EV_ABS:
+        switch (rawEvent->scanCode) {
+        case ABS_X:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_X;
+            mAccumulator.absX = rawEvent->value;
+            break;
+        case ABS_Y:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_Y;
+            mAccumulator.absY = rawEvent->value;
+            break;
+        case ABS_PRESSURE:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_PRESSURE;
+            mAccumulator.absPressure = rawEvent->value;
+            break;
+        case ABS_TOOL_WIDTH:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_TOOL_WIDTH;
+            mAccumulator.absToolWidth = rawEvent->value;
+            break;
+        }
+        break;
+
+    case EV_SYN:
+        switch (rawEvent->scanCode) {
+        case SYN_REPORT:
+            sync(rawEvent->when);
+            break;
+        }
+        break;
+    }
+}
+
+void SingleTouchInputMapper::sync(nsecs_t when) {
+    uint32_t fields = mAccumulator.fields;
+    if (fields == 0) {
+        return; // no new state changes, so nothing to do
+    }
+
+    if (fields & Accumulator::FIELD_BTN_TOUCH) {
+        mDown = mAccumulator.btnTouch;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_X) {
+        mX = mAccumulator.absX;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_Y) {
+        mY = mAccumulator.absY;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_PRESSURE) {
+        mPressure = mAccumulator.absPressure;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_TOOL_WIDTH) {
+        mToolWidth = mAccumulator.absToolWidth;
+    }
+
+    mCurrentTouch.clear();
+
+    if (mDown) {
+        mCurrentTouch.pointerCount = 1;
+        mCurrentTouch.pointers[0].id = 0;
+        mCurrentTouch.pointers[0].x = mX;
+        mCurrentTouch.pointers[0].y = mY;
+        mCurrentTouch.pointers[0].pressure = mPressure;
+        mCurrentTouch.pointers[0].touchMajor = 0;
+        mCurrentTouch.pointers[0].touchMinor = 0;
+        mCurrentTouch.pointers[0].toolMajor = mToolWidth;
+        mCurrentTouch.pointers[0].toolMinor = mToolWidth;
+        mCurrentTouch.pointers[0].orientation = 0;
+        mCurrentTouch.idToIndex[0] = 0;
+        mCurrentTouch.idBits.markBit(0);
+    }
+
+    syncTouch(when, true);
+
+    mAccumulator.clear();
+}
+
+void SingleTouchInputMapper::configureRawAxes() {
+    TouchInputMapper::configureRawAxes();
+
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mRawAxes.x);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mRawAxes.y);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mRawAxes.pressure);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mRawAxes.toolMajor);
+}
+
+
+// --- MultiTouchInputMapper ---
+
+MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
+        TouchInputMapper(device) {
+    initialize();
+}
+
+MultiTouchInputMapper::~MultiTouchInputMapper() {
+}
+
+void MultiTouchInputMapper::initialize() {
+    mAccumulator.clear();
+}
+
+void MultiTouchInputMapper::reset() {
+    TouchInputMapper::reset();
+
+    initialize();
+}
+
+void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_ABS: {
+        uint32_t pointerIndex = mAccumulator.pointerCount;
+        Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex];
+
+        switch (rawEvent->scanCode) {
+        case ABS_MT_POSITION_X:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_X;
+            pointer->absMTPositionX = rawEvent->value;
+            break;
+        case ABS_MT_POSITION_Y:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y;
+            pointer->absMTPositionY = rawEvent->value;
+            break;
+        case ABS_MT_TOUCH_MAJOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
+            pointer->absMTTouchMajor = rawEvent->value;
+            break;
+        case ABS_MT_TOUCH_MINOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR;
+            pointer->absMTTouchMinor = rawEvent->value;
+            break;
+        case ABS_MT_WIDTH_MAJOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
+            pointer->absMTWidthMajor = rawEvent->value;
+            break;
+        case ABS_MT_WIDTH_MINOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR;
+            pointer->absMTWidthMinor = rawEvent->value;
+            break;
+        case ABS_MT_ORIENTATION:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION;
+            pointer->absMTOrientation = rawEvent->value;
+            break;
+        case ABS_MT_TRACKING_ID:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID;
+            pointer->absMTTrackingId = rawEvent->value;
+            break;
+        case ABS_MT_PRESSURE:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_PRESSURE;
+            pointer->absMTPressure = rawEvent->value;
+            break;
+        }
+        break;
+    }
+
+    case EV_SYN:
+        switch (rawEvent->scanCode) {
+        case SYN_MT_REPORT: {
+            // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+            uint32_t pointerIndex = mAccumulator.pointerCount;
+
+            if (mAccumulator.pointers[pointerIndex].fields) {
+                if (pointerIndex == MAX_POINTERS) {
+                    LOGW("MultiTouch device driver returned more than maximum of %d pointers.",
+                            MAX_POINTERS);
+                } else {
+                    pointerIndex += 1;
+                    mAccumulator.pointerCount = pointerIndex;
+                }
+            }
+
+            mAccumulator.pointers[pointerIndex].clear();
+            break;
+        }
+
+        case SYN_REPORT:
+            sync(rawEvent->when);
+            break;
+        }
+        break;
+    }
+}
+
+void MultiTouchInputMapper::sync(nsecs_t when) {
+    static const uint32_t REQUIRED_FIELDS =
+            Accumulator::FIELD_ABS_MT_POSITION_X | Accumulator::FIELD_ABS_MT_POSITION_Y;
+
+    uint32_t inCount = mAccumulator.pointerCount;
+    uint32_t outCount = 0;
+    bool havePointerIds = true;
+
+    mCurrentTouch.clear();
+
+    for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) {
+        const Accumulator::Pointer& inPointer = mAccumulator.pointers[inIndex];
+        uint32_t fields = inPointer.fields;
+
+        if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
+            // Some drivers send empty MT sync packets without X / Y to indicate a pointer up.
+            // Drop this finger.
+            continue;
+        }
+
+        PointerData& outPointer = mCurrentTouch.pointers[outCount];
+        outPointer.x = inPointer.absMTPositionX;
+        outPointer.y = inPointer.absMTPositionY;
+
+        if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
+            if (inPointer.absMTPressure <= 0) {
+                // Some devices send sync packets with X / Y but with a 0 pressure to indicate
+                // a pointer going up.  Drop this finger.
+                continue;
+            }
+            outPointer.pressure = inPointer.absMTPressure;
+        } else {
+            // Default pressure to 0 if absent.
+            outPointer.pressure = 0;
+        }
+
+        if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MAJOR) {
+            if (inPointer.absMTTouchMajor <= 0) {
+                // Some devices send sync packets with X / Y but with a 0 touch major to indicate
+                // a pointer going up.  Drop this finger.
+                continue;
+            }
+            outPointer.touchMajor = inPointer.absMTTouchMajor;
+        } else {
+            // Default touch area to 0 if absent.
+            outPointer.touchMajor = 0;
+        }
+
+        if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) {
+            outPointer.touchMinor = inPointer.absMTTouchMinor;
+        } else {
+            // Assume touch area is circular.
+            outPointer.touchMinor = outPointer.touchMajor;
+        }
+
+        if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MAJOR) {
+            outPointer.toolMajor = inPointer.absMTWidthMajor;
+        } else {
+            // Default tool area to 0 if absent.
+            outPointer.toolMajor = 0;
+        }
+
+        if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) {
+            outPointer.toolMinor = inPointer.absMTWidthMinor;
+        } else {
+            // Assume tool area is circular.
+            outPointer.toolMinor = outPointer.toolMajor;
+        }
+
+        if (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) {
+            outPointer.orientation = inPointer.absMTOrientation;
+        } else {
+            // Default orientation to vertical if absent.
+            outPointer.orientation = 0;
+        }
+
+        // Assign pointer id using tracking id if available.
+        if (havePointerIds) {
+            if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) {
+                uint32_t id = uint32_t(inPointer.absMTTrackingId);
+
+                if (id > MAX_POINTER_ID) {
+#if DEBUG_POINTERS
+                    LOGD("Pointers: Ignoring driver provided pointer id %d because "
+                            "it is larger than max supported id %d",
+                            id, MAX_POINTER_ID);
+#endif
+                    havePointerIds = false;
+                }
+                else {
+                    outPointer.id = id;
+                    mCurrentTouch.idToIndex[id] = outCount;
+                    mCurrentTouch.idBits.markBit(id);
+                }
+            } else {
+                havePointerIds = false;
+            }
+        }
+
+        outCount += 1;
+    }
+
+    mCurrentTouch.pointerCount = outCount;
+
+    syncTouch(when, havePointerIds);
+
+    mAccumulator.clear();
+}
+
+void MultiTouchInputMapper::configureRawAxes() {
+    TouchInputMapper::configureRawAxes();
+
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mRawAxes.x);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mRawAxes.y);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mRawAxes.touchMajor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mRawAxes.touchMinor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mRawAxes.toolMajor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mRawAxes.toolMinor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mRawAxes.orientation);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_PRESSURE, & mRawAxes.pressure);
+}
+
+
+} // namespace android
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
new file mode 100644
index 0000000..8c849c4
--- /dev/null
+++ b/services/input/InputReader.h
@@ -0,0 +1,955 @@
+/*
+ * 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 "InputDispatcher.h"
+#include "PointerController.h"
+
+#include <ui/Input.h>
+#include <ui/DisplayInfo.h>
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+namespace android {
+
+class InputDevice;
+class InputMapper;
+
+
+/*
+ * Input reader policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI.  This interface is also mocked in the unit tests.
+ */
+class InputReaderPolicyInterface : public virtual RefBase {
+protected:
+    InputReaderPolicyInterface() { }
+    virtual ~InputReaderPolicyInterface() { }
+
+public:
+    /* Display orientations. */
+    enum {
+        ROTATION_0 = 0,
+        ROTATION_90 = 1,
+        ROTATION_180 = 2,
+        ROTATION_270 = 3
+    };
+
+    /* Gets information about the display with the specified id.
+     * Returns true if the display info is available, false otherwise.
+     */
+    virtual bool getDisplayInfo(int32_t displayId,
+            int32_t* width, int32_t* height, int32_t* orientation) = 0;
+
+    /* Determines whether to turn on some hacks we have to improve the touch interaction with a
+     * certain device whose screen currently is not all that good.
+     */
+    virtual bool filterTouchEvents() = 0;
+
+    /* Determines whether to turn on some hacks to improve touch interaction with another device
+     * where touch coordinate data can get corrupted.
+     */
+    virtual bool filterJumpyTouchEvents() = 0;
+
+    /* Gets the excluded device names for the platform. */
+    virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) = 0;
+
+    /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
+    virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+};
+
+
+/* Processes raw input events and sends cooked event data to an input dispatcher. */
+class InputReaderInterface : public virtual RefBase {
+protected:
+    InputReaderInterface() { }
+    virtual ~InputReaderInterface() { }
+
+public:
+    /* Dumps the state of the input reader.
+     *
+     * This method may be called on any thread (usually by the input manager). */
+    virtual void dump(String8& dump) = 0;
+
+    /* Runs a single iteration of the processing loop.
+     * Nominally reads and processes one incoming message from the EventHub.
+     *
+     * This method should be called on the input reader thread.
+     */
+    virtual void loopOnce() = 0;
+
+    /* Gets the current input device configuration.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
+
+    /* Gets information about the specified input device.
+     * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
+     * was no such device.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
+
+    /* Gets the list of all registered device ids. */
+    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
+
+    /* Query current input state. */
+    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t scanCode) = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t keyCode) = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+            int32_t sw) = 0;
+
+    /* Determine whether physical keys exist for the given framework-domain key codes. */
+    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+};
+
+
+/* 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 InputReaderPolicyInterface* getPolicy() = 0;
+    virtual InputDispatcherInterface* getDispatcher() = 0;
+    virtual EventHubInterface* getEventHub() = 0;
+};
+
+
+/* The input reader reads raw event data from the event hub and processes it into input events
+ * that it sends to the input dispatcher.  Some functions of the input reader, such as early
+ * event filtering in low power states, are controlled by a separate policy object.
+ *
+ * IMPORTANT INVARIANT:
+ *     Because the policy and dispatcher can potentially block or cause re-entrance into
+ *     the input reader, the input reader never calls into other components while holding
+ *     an exclusive internal lock whenever re-entrance can happen.
+ */
+class InputReader : public InputReaderInterface, protected InputReaderContext {
+public:
+    InputReader(const sp<EventHubInterface>& eventHub,
+            const sp<InputReaderPolicyInterface>& policy,
+            const sp<InputDispatcherInterface>& dispatcher);
+    virtual ~InputReader();
+
+    virtual void dump(String8& dump);
+
+    virtual void loopOnce();
+
+    virtual void getInputConfiguration(InputConfiguration* outConfiguration);
+
+    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
+    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
+
+    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 bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
+
+protected:
+    // These methods are protected virtual so they can be overridden and instrumented
+    // by test cases.
+    virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
+
+private:
+    sp<EventHubInterface> mEventHub;
+    sp<InputReaderPolicyInterface> mPolicy;
+    sp<InputDispatcherInterface> mDispatcher;
+
+    virtual InputReaderPolicyInterface* getPolicy() { return mPolicy.get(); }
+    virtual InputDispatcherInterface* getDispatcher() { return mDispatcher.get(); }
+    virtual EventHubInterface* getEventHub() { return mEventHub.get(); }
+
+    // This reader/writer lock guards the list of input devices.
+    // The writer lock must be held whenever the list of input devices is modified
+    //   and then promptly released.
+    // The reader lock must be held whenever the list of input devices is traversed or an
+    //   input device in the list is accessed.
+    // This lock only protects the registry and prevents inadvertent deletion of device objects
+    // that are in use.  Individual devices are responsible for guarding their own internal state
+    // as needed for concurrent operation.
+    RWLock mDeviceRegistryLock;
+    KeyedVector<int32_t, InputDevice*> mDevices;
+
+    // low-level input event decoding and device management
+    void process(const RawEvent* rawEvent);
+
+    void addDevice(int32_t deviceId);
+    void removeDevice(int32_t deviceId);
+    void configureExcludedDevices();
+
+    void consumeEvent(const RawEvent* rawEvent);
+
+    void handleConfigurationChanged(nsecs_t when);
+
+    // state management for all devices
+    Mutex mStateLock;
+
+    int32_t mGlobalMetaState;
+    virtual void updateGlobalMetaState();
+    virtual int32_t getGlobalMetaState();
+
+    InputConfiguration mInputConfiguration;
+    void updateInputConfiguration();
+
+    // state queries
+    typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+    int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
+            GetStateFunc getStateFunc);
+    bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags);
+};
+
+
+/* Reads raw events from the event hub and processes them, endlessly. */
+class InputReaderThread : public Thread {
+public:
+    InputReaderThread(const sp<InputReaderInterface>& reader);
+    virtual ~InputReaderThread();
+
+private:
+    sp<InputReaderInterface> mReader;
+
+    virtual bool threadLoop();
+};
+
+
+/* Represents the state of a single input device. */
+class InputDevice {
+public:
+    InputDevice(InputReaderContext* context, int32_t id, const String8& name);
+    ~InputDevice();
+
+    inline InputReaderContext* getContext() { return mContext; }
+    inline int32_t getId() { return mId; }
+    inline const String8& getName() { return mName; }
+    inline uint32_t getSources() { return mSources; }
+
+    inline bool isIgnored() { return mMappers.isEmpty(); }
+
+    void dump(String8& dump);
+    void addMapper(InputMapper* mapper);
+    void configure();
+    void reset();
+    void process(const RawEvent* rawEvent);
+
+    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);
+
+    int32_t getMetaState();
+
+    inline const PropertyMap& getConfiguration() {
+        return mConfiguration;
+    }
+
+private:
+    InputReaderContext* mContext;
+    int32_t mId;
+
+    Vector<InputMapper*> mMappers;
+
+    String8 mName;
+    uint32_t mSources;
+
+    typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+    int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+
+    PropertyMap mConfiguration;
+};
+
+
+/* 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.
+ */
+class InputMapper {
+public:
+    InputMapper(InputDevice* device);
+    virtual ~InputMapper();
+
+    inline InputDevice* getDevice() { return mDevice; }
+    inline int32_t getDeviceId() { return mDevice->getId(); }
+    inline const String8 getDeviceName() { return mDevice->getName(); }
+    inline InputReaderContext* getContext() { return mContext; }
+    inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); }
+    inline InputDispatcherInterface* getDispatcher() { return mContext->getDispatcher(); }
+    inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+    virtual uint32_t getSources() = 0;
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
+    virtual void configure();
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent) = 0;
+
+    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 int32_t getMetaState();
+
+protected:
+    InputDevice* mDevice;
+    InputReaderContext* mContext;
+};
+
+
+class SwitchInputMapper : public InputMapper {
+public:
+    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);
+
+private:
+    void processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue);
+};
+
+
+class KeyboardInputMapper : public InputMapper {
+public:
+    KeyboardInputMapper(InputDevice* device, uint32_t sources, int32_t keyboardType);
+    virtual ~KeyboardInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
+    virtual void configure();
+    virtual void reset();
+    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();
+
+private:
+    Mutex mLock;
+
+    struct KeyDown {
+        int32_t keyCode;
+        int32_t scanCode;
+    };
+
+    uint32_t mSources;
+    int32_t mKeyboardType;
+
+    // Immutable configuration parameters.
+    struct Parameters {
+        int32_t associatedDisplayId;
+        bool orientationAware;
+    } mParameters;
+
+    struct LockedState {
+        Vector<KeyDown> keyDowns; // keys that are down
+        int32_t metaState;
+        nsecs_t downTime; // time of most recent key down
+
+        struct LedState {
+            bool avail; // led is available
+            bool on;    // we think the led is currently on
+        };
+        LedState capsLockLedState;
+        LedState numLockLedState;
+        LedState scrollLockLedState;
+    } mLocked;
+
+    void initializeLocked();
+
+    void configureParameters();
+    void dumpParameters(String8& dump);
+
+    bool isKeyboardOrGamepadKey(int32_t scanCode);
+
+    void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
+            uint32_t policyFlags);
+
+    ssize_t findKeyDownLocked(int32_t scanCode);
+
+    void resetLedStateLocked();
+    void initializeLedStateLocked(LockedState::LedState& ledState, int32_t led);
+    void updateLedStateLocked(bool reset);
+    void updateLedStateForModifierLocked(LockedState::LedState& ledState, int32_t led,
+            int32_t modifier, bool reset);
+};
+
+
+class CursorInputMapper : public InputMapper {
+public:
+    CursorInputMapper(InputDevice* device);
+    virtual ~CursorInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
+    virtual void configure();
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+
+private:
+    // Amount that trackball needs to move in order to generate a key event.
+    static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+    Mutex mLock;
+
+    // Immutable configuration parameters.
+    struct Parameters {
+        enum Mode {
+            MODE_POINTER,
+            MODE_NAVIGATION,
+        };
+
+        Mode mode;
+        int32_t associatedDisplayId;
+        bool orientationAware;
+    } mParameters;
+
+    struct Accumulator {
+        enum {
+            FIELD_BTN_MOUSE = 1,
+            FIELD_REL_X = 2,
+            FIELD_REL_Y = 4
+        };
+
+        uint32_t fields;
+
+        bool btnMouse;
+        int32_t relX;
+        int32_t relY;
+
+        inline void clear() {
+            fields = 0;
+        }
+    } mAccumulator;
+
+    int32_t mSources;
+    float mXScale;
+    float mYScale;
+    float mXPrecision;
+    float mYPrecision;
+    sp<PointerControllerInterface> mPointerController;
+
+    struct LockedState {
+        bool down;
+        nsecs_t downTime;
+    } mLocked;
+
+    void initializeLocked();
+
+    void configureParameters();
+    void dumpParameters(String8& dump);
+
+    void sync(nsecs_t when);
+};
+
+
+class TouchInputMapper : public InputMapper {
+public:
+    TouchInputMapper(InputDevice* device);
+    virtual ~TouchInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
+    virtual void configure();
+    virtual void reset();
+
+    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);
+
+protected:
+    Mutex mLock;
+
+    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;
+        }
+    };
+
+    // Raw data for a single pointer.
+    struct PointerData {
+        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;
+
+        inline bool operator== (const PointerData& other) const {
+            return id == other.id
+                    && x == other.x
+                    && y == other.y
+                    && pressure == other.pressure
+                    && touchMajor == other.touchMajor
+                    && touchMinor == other.touchMinor
+                    && toolMajor == other.toolMajor
+                    && toolMinor == other.toolMinor
+                    && orientation == other.orientation;
+        }
+        inline bool operator!= (const PointerData& other) const {
+            return !(*this == other);
+        }
+    };
+
+    // Raw data for a collection of pointers including a pointer id mapping table.
+    struct TouchData {
+        uint32_t pointerCount;
+        PointerData pointers[MAX_POINTERS];
+        BitSet32 idBits;
+        uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+        void copyFrom(const TouchData& other) {
+            pointerCount = other.pointerCount;
+            idBits = other.idBits;
+
+            for (uint32_t i = 0; i < pointerCount; i++) {
+                pointers[i] = other.pointers[i];
+
+                int id = pointers[i].id;
+                idToIndex[id] = other.idToIndex[id];
+            }
+        }
+
+        inline void clear() {
+            pointerCount = 0;
+            idBits.clear();
+        }
+    };
+
+    // Input sources supported by the device.
+    int32_t mSources;
+
+    // Immutable configuration parameters.
+    struct Parameters {
+        enum DeviceType {
+            DEVICE_TYPE_TOUCH_SCREEN,
+            DEVICE_TYPE_TOUCH_PAD,
+        };
+
+        DeviceType deviceType;
+        int32_t associatedDisplayId;
+        bool orientationAware;
+
+        bool useBadTouchFilter;
+        bool useJumpyTouchFilter;
+        bool useAveragingTouchFilter;
+    } mParameters;
+
+    // Immutable calibration parameters in parsed form.
+    struct Calibration {
+        // Position
+        bool haveXOrigin;
+        int32_t xOrigin;
+        bool haveYOrigin;
+        int32_t yOrigin;
+        bool haveXScale;
+        float xScale;
+        bool haveYScale;
+        float yScale;
+
+        // Touch Size
+        enum TouchSizeCalibration {
+            TOUCH_SIZE_CALIBRATION_DEFAULT,
+            TOUCH_SIZE_CALIBRATION_NONE,
+            TOUCH_SIZE_CALIBRATION_GEOMETRIC,
+            TOUCH_SIZE_CALIBRATION_PRESSURE,
+        };
+
+        TouchSizeCalibration touchSizeCalibration;
+
+        // Tool Size
+        enum ToolSizeCalibration {
+            TOOL_SIZE_CALIBRATION_DEFAULT,
+            TOOL_SIZE_CALIBRATION_NONE,
+            TOOL_SIZE_CALIBRATION_GEOMETRIC,
+            TOOL_SIZE_CALIBRATION_LINEAR,
+            TOOL_SIZE_CALIBRATION_AREA,
+        };
+
+        ToolSizeCalibration toolSizeCalibration;
+        bool haveToolSizeLinearScale;
+        float toolSizeLinearScale;
+        bool haveToolSizeLinearBias;
+        float toolSizeLinearBias;
+        bool haveToolSizeAreaScale;
+        float toolSizeAreaScale;
+        bool haveToolSizeAreaBias;
+        float toolSizeAreaBias;
+        bool haveToolSizeIsSummed;
+        bool toolSizeIsSummed;
+
+        // Pressure
+        enum PressureCalibration {
+            PRESSURE_CALIBRATION_DEFAULT,
+            PRESSURE_CALIBRATION_NONE,
+            PRESSURE_CALIBRATION_PHYSICAL,
+            PRESSURE_CALIBRATION_AMPLITUDE,
+        };
+        enum PressureSource {
+            PRESSURE_SOURCE_DEFAULT,
+            PRESSURE_SOURCE_PRESSURE,
+            PRESSURE_SOURCE_TOUCH,
+        };
+
+        PressureCalibration pressureCalibration;
+        PressureSource pressureSource;
+        bool havePressureScale;
+        float pressureScale;
+
+        // Size
+        enum SizeCalibration {
+            SIZE_CALIBRATION_DEFAULT,
+            SIZE_CALIBRATION_NONE,
+            SIZE_CALIBRATION_NORMALIZED,
+        };
+
+        SizeCalibration sizeCalibration;
+
+        // Orientation
+        enum OrientationCalibration {
+            ORIENTATION_CALIBRATION_DEFAULT,
+            ORIENTATION_CALIBRATION_NONE,
+            ORIENTATION_CALIBRATION_INTERPOLATED,
+        };
+
+        OrientationCalibration orientationCalibration;
+    } mCalibration;
+
+    // Raw axis information from the driver.
+    struct RawAxes {
+        RawAbsoluteAxisInfo x;
+        RawAbsoluteAxisInfo y;
+        RawAbsoluteAxisInfo pressure;
+        RawAbsoluteAxisInfo touchMajor;
+        RawAbsoluteAxisInfo touchMinor;
+        RawAbsoluteAxisInfo toolMajor;
+        RawAbsoluteAxisInfo toolMinor;
+        RawAbsoluteAxisInfo orientation;
+    } mRawAxes;
+
+    // Current and previous touch sample data.
+    TouchData mCurrentTouch;
+    TouchData mLastTouch;
+
+    // The time the primary pointer last went down.
+    nsecs_t mDownTime;
+
+    struct LockedState {
+        Vector<VirtualKey> virtualKeys;
+
+        // The surface orientation and width and height set by configureSurfaceLocked().
+        int32_t surfaceOrientation;
+        int32_t surfaceWidth, surfaceHeight;
+
+        // Translation and scaling factors, orientation-independent.
+        int32_t xOrigin;
+        float xScale;
+        float xPrecision;
+
+        int32_t yOrigin;
+        float yScale;
+        float yPrecision;
+
+        float geometricScale;
+
+        float toolSizeLinearScale;
+        float toolSizeLinearBias;
+        float toolSizeAreaScale;
+        float toolSizeAreaBias;
+
+        float pressureScale;
+
+        float sizeScale;
+
+        float orientationScale;
+
+        // Oriented motion ranges for input device info.
+        struct OrientedRanges {
+            InputDeviceInfo::MotionRange x;
+            InputDeviceInfo::MotionRange y;
+
+            bool havePressure;
+            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;
+        } orientedRanges;
+
+        // Oriented dimensions and precision.
+        float orientedSurfaceWidth, orientedSurfaceHeight;
+        float orientedXPrecision, orientedYPrecision;
+
+        struct CurrentVirtualKeyState {
+            bool down;
+            nsecs_t downTime;
+            int32_t keyCode;
+            int32_t scanCode;
+        } currentVirtualKey;
+    } mLocked;
+
+    virtual void configureParameters();
+    virtual void dumpParameters(String8& dump);
+    virtual void configureRawAxes();
+    virtual void dumpRawAxes(String8& dump);
+    virtual bool configureSurfaceLocked();
+    virtual void dumpSurfaceLocked(String8& dump);
+    virtual void configureVirtualKeysLocked();
+    virtual void dumpVirtualKeysLocked(String8& dump);
+    virtual void parseCalibration();
+    virtual void resolveCalibration();
+    virtual void dumpCalibration(String8& dump);
+
+    enum TouchResult {
+        // Dispatch the touch normally.
+        DISPATCH_TOUCH,
+        // Do not dispatch the touch, but keep tracking the current stroke.
+        SKIP_TOUCH,
+        // Do not dispatch the touch, and drop all information associated with the current stoke
+        // so the next movement will appear as a new down.
+        DROP_STROKE
+    };
+
+    void syncTouch(nsecs_t when, bool havePointerIds);
+
+private:
+    /* Maximum number of historical samples to average. */
+    static const uint32_t AVERAGING_HISTORY_SIZE = 5;
+
+    /* Slop distance for jumpy pointer detection.
+     * The vertical range of the screen divided by this is our epsilon value. */
+    static const uint32_t JUMPY_EPSILON_DIVISOR = 212;
+
+    /* Number of jumpy points to drop for touchscreens that need it. */
+    static const uint32_t JUMPY_TRANSITION_DROPS = 3;
+    static const uint32_t JUMPY_DROP_LIMIT = 3;
+
+    /* Maximum squared distance for averaging.
+     * If moving farther than this, turn of averaging to avoid lag in response. */
+    static const uint64_t AVERAGING_DISTANCE_LIMIT = 75 * 75;
+
+    struct AveragingTouchFilterState {
+        // Individual history tracks are stored by pointer id
+        uint32_t historyStart[MAX_POINTERS];
+        uint32_t historyEnd[MAX_POINTERS];
+        struct {
+            struct {
+                int32_t x;
+                int32_t y;
+                int32_t pressure;
+            } pointers[MAX_POINTERS];
+        } historyData[AVERAGING_HISTORY_SIZE];
+    } mAveragingTouchFilter;
+
+    struct JumpyTouchFilterState {
+        uint32_t jumpyPointsDropped;
+    } mJumpyTouchFilter;
+
+    struct PointerDistanceHeapElement {
+        uint32_t currentPointerIndex : 8;
+        uint32_t lastPointerIndex : 8;
+        uint64_t distance : 48; // squared distance
+    };
+
+    void initializeLocked();
+
+    TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
+    void dispatchTouches(nsecs_t when, uint32_t policyFlags);
+    void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch,
+            BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
+            int32_t motionEventAction);
+
+    bool isPointInsideSurfaceLocked(int32_t x, int32_t y);
+    const VirtualKey* findVirtualKeyHitLocked(int32_t x, int32_t y);
+
+    bool applyBadTouchFilter();
+    bool applyJumpyTouchFilter();
+    void applyAveragingTouchFilter();
+    void calculatePointerIds();
+};
+
+
+class SingleTouchInputMapper : public TouchInputMapper {
+public:
+    SingleTouchInputMapper(InputDevice* device);
+    virtual ~SingleTouchInputMapper();
+
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+protected:
+    virtual void configureRawAxes();
+
+private:
+    struct Accumulator {
+        enum {
+            FIELD_BTN_TOUCH = 1,
+            FIELD_ABS_X = 2,
+            FIELD_ABS_Y = 4,
+            FIELD_ABS_PRESSURE = 8,
+            FIELD_ABS_TOOL_WIDTH = 16
+        };
+
+        uint32_t fields;
+
+        bool btnTouch;
+        int32_t absX;
+        int32_t absY;
+        int32_t absPressure;
+        int32_t absToolWidth;
+
+        inline void clear() {
+            fields = 0;
+        }
+    } mAccumulator;
+
+    bool mDown;
+    int32_t mX;
+    int32_t mY;
+    int32_t mPressure;
+    int32_t mToolWidth;
+
+    void initialize();
+
+    void sync(nsecs_t when);
+};
+
+
+class MultiTouchInputMapper : public TouchInputMapper {
+public:
+    MultiTouchInputMapper(InputDevice* device);
+    virtual ~MultiTouchInputMapper();
+
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+protected:
+    virtual void configureRawAxes();
+
+private:
+    struct Accumulator {
+        enum {
+            FIELD_ABS_MT_POSITION_X = 1,
+            FIELD_ABS_MT_POSITION_Y = 2,
+            FIELD_ABS_MT_TOUCH_MAJOR = 4,
+            FIELD_ABS_MT_TOUCH_MINOR = 8,
+            FIELD_ABS_MT_WIDTH_MAJOR = 16,
+            FIELD_ABS_MT_WIDTH_MINOR = 32,
+            FIELD_ABS_MT_ORIENTATION = 64,
+            FIELD_ABS_MT_TRACKING_ID = 128,
+            FIELD_ABS_MT_PRESSURE = 256,
+        };
+
+        uint32_t pointerCount;
+        struct Pointer {
+            uint32_t fields;
+
+            int32_t absMTPositionX;
+            int32_t absMTPositionY;
+            int32_t absMTTouchMajor;
+            int32_t absMTTouchMinor;
+            int32_t absMTWidthMajor;
+            int32_t absMTWidthMinor;
+            int32_t absMTOrientation;
+            int32_t absMTTrackingId;
+            int32_t absMTPressure;
+
+            inline void clear() {
+                fields = 0;
+            }
+        } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks
+
+        inline void clear() {
+            pointerCount = 0;
+            pointers[0].clear();
+        }
+    } mAccumulator;
+
+    void initialize();
+
+    void sync(nsecs_t when);
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_READER_H
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
new file mode 100644
index 0000000..ebc58ee
--- /dev/null
+++ b/services/input/PointerController.cpp
@@ -0,0 +1,407 @@
+/*
+ * 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 "PointerController"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about pointer updates
+#define DEBUG_POINTER_UPDATES 0
+
+#include "PointerController.h"
+
+#include <cutils/log.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+
+namespace android {
+
+// --- PointerController ---
+
+PointerController::PointerController(int32_t pointerLayer) :
+    mPointerLayer(pointerLayer) {
+    AutoMutex _l(mLock);
+
+    mLocked.displayWidth = -1;
+    mLocked.displayHeight = -1;
+    mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+
+    mLocked.pointerX = 0;
+    mLocked.pointerY = 0;
+    mLocked.buttonState = 0;
+
+    mLocked.iconBitmap = NULL;
+    mLocked.iconHotSpotX = 0;
+    mLocked.iconHotSpotY = 0;
+
+    mLocked.wantVisible = false;
+    mLocked.visible = false;
+    mLocked.drawn = false;
+}
+
+PointerController::~PointerController() {
+    if (mSurfaceControl != NULL) {
+        mSurfaceControl->clear();
+        mSurfaceControl.clear();
+    }
+
+    if (mSurfaceComposerClient != NULL) {
+        mSurfaceComposerClient->dispose();
+        mSurfaceComposerClient.clear();
+    }
+
+    delete mLocked.iconBitmap;
+}
+
+bool PointerController::getBounds(float* outMinX, float* outMinY,
+        float* outMaxX, float* outMaxY) const {
+    AutoMutex _l(mLock);
+
+    return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+}
+
+bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
+        float* outMaxX, float* outMaxY) const {
+    if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+        return false;
+    }
+
+    *outMinX = 0;
+    *outMinY = 0;
+    switch (mLocked.displayOrientation) {
+    case DISPLAY_ORIENTATION_90:
+    case DISPLAY_ORIENTATION_270:
+        *outMaxX = mLocked.displayHeight;
+        *outMaxY = mLocked.displayWidth;
+        break;
+    default:
+        *outMaxX = mLocked.displayWidth;
+        *outMaxY = mLocked.displayHeight;
+        break;
+    }
+    return true;
+}
+
+void PointerController::move(float deltaX, float deltaY) {
+#if DEBUG_POINTER_UPDATES
+    LOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
+#endif
+    if (deltaX == 0.0f && deltaY == 0.0f) {
+        return;
+    }
+
+    AutoMutex _l(mLock);
+
+    setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+}
+
+void PointerController::setButtonState(uint32_t buttonState) {
+#if DEBUG_POINTER_UPDATES
+    LOGD("Set button state 0x%08x", buttonState);
+#endif
+    AutoMutex _l(mLock);
+
+    if (mLocked.buttonState != buttonState) {
+        mLocked.buttonState = buttonState;
+        mLocked.wantVisible = true;
+        updateLocked();
+    }
+}
+
+uint32_t PointerController::getButtonState() const {
+    AutoMutex _l(mLock);
+
+    return mLocked.buttonState;
+}
+
+void PointerController::setPosition(float x, float y) {
+#if DEBUG_POINTER_UPDATES
+    LOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
+#endif
+    AutoMutex _l(mLock);
+
+    setPositionLocked(x, y);
+}
+
+void PointerController::setPositionLocked(float x, float y) {
+    float minX, minY, maxX, maxY;
+    if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+        if (x <= minX) {
+            mLocked.pointerX = minX;
+        } else if (x >= maxX) {
+            mLocked.pointerX = maxX;
+        } else {
+            mLocked.pointerX = x;
+        }
+        if (y <= minY) {
+            mLocked.pointerY = minY;
+        } else if (y >= maxY) {
+            mLocked.pointerY = maxY;
+        } else {
+            mLocked.pointerY = y;
+        }
+        mLocked.wantVisible = true;
+        updateLocked();
+    }
+}
+
+void PointerController::getPosition(float* outX, float* outY) const {
+    AutoMutex _l(mLock);
+
+    *outX = mLocked.pointerX;
+    *outY = mLocked.pointerY;
+}
+
+void PointerController::updateLocked() {
+    bool wantVisibleAndHavePointerIcon = mLocked.wantVisible && mLocked.iconBitmap;
+
+    if (wantVisibleAndHavePointerIcon) {
+        // Want the pointer to be visible.
+        // Ensure the surface is created and drawn.
+        if (!createSurfaceIfNeededLocked() || !drawPointerIfNeededLocked()) {
+            return;
+        }
+    } else {
+        // Don't want the pointer to be visible.
+        // If it is not visible then we are done.
+        if (mSurfaceControl == NULL || !mLocked.visible) {
+            return;
+        }
+    }
+
+    status_t status = mSurfaceComposerClient->openTransaction();
+    if (status) {
+        LOGE("Error opening surface transaction to update pointer surface.");
+        return;
+    }
+
+    if (wantVisibleAndHavePointerIcon) {
+        status = mSurfaceControl->setPosition(
+                mLocked.pointerX - mLocked.iconHotSpotX,
+                mLocked.pointerY - mLocked.iconHotSpotY);
+        if (status) {
+            LOGE("Error %d moving pointer surface.", status);
+            goto CloseTransaction;
+        }
+
+        if (!mLocked.visible) {
+            status = mSurfaceControl->setLayer(mPointerLayer);
+            if (status) {
+                LOGE("Error %d setting pointer surface layer.", status);
+                goto CloseTransaction;
+            }
+
+            status = mSurfaceControl->show(mPointerLayer);
+            if (status) {
+                LOGE("Error %d showing pointer surface.", status);
+                goto CloseTransaction;
+            }
+
+            mLocked.visible = true;
+        }
+    } else {
+        if (mLocked.visible) {
+            status = mSurfaceControl->hide();
+            if (status) {
+                LOGE("Error %d hiding pointer surface.", status);
+                goto CloseTransaction;
+            }
+
+            mLocked.visible = false;
+        }
+    }
+
+CloseTransaction:
+    status = mSurfaceComposerClient->closeTransaction();
+    if (status) {
+        LOGE("Error closing surface transaction to update pointer surface.");
+    }
+}
+
+void PointerController::setDisplaySize(int32_t width, int32_t height) {
+    AutoMutex _l(mLock);
+
+    if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+        mLocked.displayWidth = width;
+        mLocked.displayHeight = height;
+
+        float minX, minY, maxX, maxY;
+        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+            mLocked.pointerX = (minX + maxX) * 0.5f;
+            mLocked.pointerY = (minY + maxY) * 0.5f;
+        } else {
+            mLocked.pointerX = 0;
+            mLocked.pointerY = 0;
+        }
+
+        updateLocked();
+    }
+}
+
+void PointerController::setDisplayOrientation(int32_t orientation) {
+    AutoMutex _l(mLock);
+
+    if (mLocked.displayOrientation != orientation) {
+        float absoluteX, absoluteY;
+
+        // Map from oriented display coordinates to absolute display coordinates.
+        switch (mLocked.displayOrientation) {
+        case DISPLAY_ORIENTATION_90:
+            absoluteX = mLocked.displayWidth - mLocked.pointerY;
+            absoluteY = mLocked.pointerX;
+            break;
+        case DISPLAY_ORIENTATION_180:
+            absoluteX = mLocked.displayWidth - mLocked.pointerX;
+            absoluteY = mLocked.displayHeight - mLocked.pointerY;
+            break;
+        case DISPLAY_ORIENTATION_270:
+            absoluteX = mLocked.pointerY;
+            absoluteY = mLocked.displayHeight - mLocked.pointerX;
+            break;
+        default:
+            absoluteX = mLocked.pointerX;
+            absoluteY = mLocked.pointerY;
+            break;
+        }
+
+        // Map from absolute display coordinates to oriented display coordinates.
+        switch (orientation) {
+        case DISPLAY_ORIENTATION_90:
+            mLocked.pointerX = absoluteY;
+            mLocked.pointerY = mLocked.displayWidth - absoluteX;
+            break;
+        case DISPLAY_ORIENTATION_180:
+            mLocked.pointerX = mLocked.displayWidth - absoluteX;
+            mLocked.pointerY = mLocked.displayHeight - absoluteY;
+            break;
+        case DISPLAY_ORIENTATION_270:
+            mLocked.pointerX = mLocked.displayHeight - absoluteY;
+            mLocked.pointerY = absoluteX;
+            break;
+        default:
+            mLocked.pointerX = absoluteX;
+            mLocked.pointerY = absoluteY;
+            break;
+        }
+
+        mLocked.displayOrientation = orientation;
+
+        updateLocked();
+    }
+}
+
+void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
+    AutoMutex _l(mLock);
+
+    delete mLocked.iconBitmap;
+    mLocked.iconBitmap = bitmap ? new SkBitmap(*bitmap) : NULL;
+    mLocked.iconHotSpotX = hotSpotX;
+    mLocked.iconHotSpotY = hotSpotY;
+    mLocked.drawn = false;
+}
+
+bool PointerController::createSurfaceIfNeededLocked() {
+    if (!mLocked.iconBitmap) {
+        // If we don't have a pointer icon, then no point allocating a surface now.
+        return false;
+    }
+
+    if (mSurfaceComposerClient == NULL) {
+        mSurfaceComposerClient = new SurfaceComposerClient();
+    }
+
+    if (mSurfaceControl == NULL) {
+        mSurfaceControl = mSurfaceComposerClient->createSurface(getpid(),
+                String8("Pointer Icon"), 0,
+                mLocked.iconBitmap->width(), mLocked.iconBitmap->height(),
+                PIXEL_FORMAT_RGBA_8888);
+        if (mSurfaceControl == NULL) {
+            LOGE("Error creating pointer surface.");
+            return false;
+        }
+    }
+    return true;
+}
+
+bool PointerController::drawPointerIfNeededLocked() {
+    if (!mLocked.drawn) {
+        if (!mLocked.iconBitmap) {
+            return false;
+        }
+
+        if (!resizeSurfaceLocked(mLocked.iconBitmap->width(), mLocked.iconBitmap->height())) {
+            return false;
+        }
+
+        sp<Surface> surface = mSurfaceControl->getSurface();
+
+        Surface::SurfaceInfo surfaceInfo;
+        status_t status = surface->lock(&surfaceInfo);
+        if (status) {
+            LOGE("Error %d locking pointer surface before drawing.", status);
+            return false;
+        }
+
+        SkBitmap surfaceBitmap;
+        ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format);
+        surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, surfaceInfo.w, surfaceInfo.h, bpr);
+        surfaceBitmap.setPixels(surfaceInfo.bits);
+
+        SkCanvas surfaceCanvas;
+        surfaceCanvas.setBitmapDevice(surfaceBitmap);
+
+        SkPaint paint;
+        paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+        surfaceCanvas.drawBitmap(*mLocked.iconBitmap, 0, 0, &paint);
+
+        status = surface->unlockAndPost();
+        if (status) {
+            LOGE("Error %d unlocking pointer surface after drawing.", status);
+            return false;
+        }
+    }
+
+    mLocked.drawn = true;
+    return true;
+}
+
+bool PointerController::resizeSurfaceLocked(int32_t width, int32_t height) {
+    status_t status = mSurfaceComposerClient->openTransaction();
+    if (status) {
+        LOGE("Error opening surface transaction to resize pointer surface.");
+        return false;
+    }
+
+    status = mSurfaceControl->setSize(width, height);
+    if (status) {
+        LOGE("Error %d setting pointer surface size.", status);
+        return false;
+    }
+
+    status = mSurfaceComposerClient->closeTransaction();
+    if (status) {
+        LOGE("Error closing surface transaction to resize pointer surface.");
+        return false;
+    }
+
+    return true;
+}
+
+} // namespace android
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
new file mode 100644
index 0000000..a2a9955
--- /dev/null
+++ b/services/input/PointerController.h
@@ -0,0 +1,129 @@
+/*
+ * 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_POINTER_CONTROLLER_H
+#define _UI_POINTER_CONTROLLER_H
+
+#include <ui/DisplayInfo.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+#include <surfaceflinger/Surface.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
+#include <surfaceflinger/ISurfaceComposer.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+enum {
+    POINTER_BUTTON_1 = 1 << 0,
+};
+
+/**
+ * Interface for tracking a single (mouse) pointer.
+ *
+ * The pointer controller is responsible for providing synchronization and for tracking
+ * display orientation changes if needed.
+ */
+class PointerControllerInterface : public virtual RefBase {
+protected:
+    PointerControllerInterface() { }
+    virtual ~PointerControllerInterface() { }
+
+public:
+    /* Gets the bounds of the region that the pointer can traverse.
+     * Returns true if the bounds are available. */
+    virtual bool getBounds(float* outMinX, float* outMinY,
+            float* outMaxX, float* outMaxY) const = 0;
+
+    /* Move the pointer. */
+    virtual void move(float deltaX, float deltaY) = 0;
+
+    /* Sets a mask that indicates which buttons are pressed. */
+    virtual void setButtonState(uint32_t buttonState) = 0;
+
+    /* Gets a mask that indicates which buttons are pressed. */
+    virtual uint32_t getButtonState() const = 0;
+
+    /* Sets the absolute location of the pointer. */
+    virtual void setPosition(float x, float y) = 0;
+
+    /* Gets the absolute location of the pointer. */
+    virtual void getPosition(float* outX, float* outY) const = 0;
+};
+
+
+/*
+ * Tracks pointer movements and draws the pointer sprite to a surface.
+ *
+ * Handles pointer acceleration and animation.
+ */
+class PointerController : public PointerControllerInterface {
+protected:
+    virtual ~PointerController();
+
+public:
+    PointerController(int32_t pointerLayer);
+
+    virtual bool getBounds(float* outMinX, float* outMinY,
+            float* outMaxX, float* outMaxY) const;
+    virtual void move(float deltaX, float deltaY);
+    virtual void setButtonState(uint32_t buttonState);
+    virtual uint32_t getButtonState() const;
+    virtual void setPosition(float x, float y);
+    virtual void getPosition(float* outX, float* outY) const;
+
+    void setDisplaySize(int32_t width, int32_t height);
+    void setDisplayOrientation(int32_t orientation);
+    void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+
+private:
+    mutable Mutex mLock;
+
+    int32_t mPointerLayer;
+    sp<SurfaceComposerClient> mSurfaceComposerClient;
+    sp<SurfaceControl> mSurfaceControl;
+
+    struct Locked {
+        int32_t displayWidth;
+        int32_t displayHeight;
+        int32_t displayOrientation;
+
+        float pointerX;
+        float pointerY;
+        uint32_t buttonState;
+
+        SkBitmap* iconBitmap;
+        float iconHotSpotX;
+        float iconHotSpotY;
+
+        bool wantVisible;
+        bool visible;
+        bool drawn;
+    } mLocked;
+
+    bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    void setPositionLocked(float x, float y);
+    void updateLocked();
+    bool createSurfaceIfNeededLocked();
+    bool drawPointerIfNeededLocked();
+    bool resizeSurfaceLocked(int32_t width, int32_t height);
+};
+
+} // namespace android
+
+#endif // _UI_POINTER_CONTROLLER_H
diff --git a/services/input/tests/Android.mk b/services/input/tests/Android.mk
new file mode 100644
index 0000000..799eb76
--- /dev/null
+++ b/services/input/tests/Android.mk
@@ -0,0 +1,50 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+# Build the unit tests.
+test_src_files := \
+    InputReader_test.cpp \
+    InputDispatcher_test.cpp
+
+shared_libraries := \
+    libcutils \
+    libutils \
+    libhardware \
+    libhardware_legacy \
+    libui \
+    libsurfaceflinger_client \
+    libskia \
+    libstlport \
+    libinput
+
+static_libraries := \
+    libgtest \
+    libgtest_main
+
+c_includes := \
+    bionic \
+    bionic/libstdc++/include \
+    external/gtest/include \
+    external/stlport/stlport \
+    external/skia/include/core
+
+module_tags := eng tests
+
+$(foreach file,$(test_src_files), \
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+    $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+    $(eval LOCAL_C_INCLUDES := $(c_includes)) \
+    $(eval LOCAL_SRC_FILES := $(file)) \
+    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+    $(eval LOCAL_MODULE_TAGS := $(module_tags)) \
+    $(eval include $(BUILD_EXECUTABLE)) \
+)
+
+# Build the manual test programs.
+include $(call all-subdir-makefiles)
+
+endif
\ No newline at end of file
diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp
new file mode 100644
index 0000000..b79633a
--- /dev/null
+++ b/services/input/tests/InputDispatcher_test.cpp
@@ -0,0 +1,230 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include "../InputDispatcher.h"
+
+#include <gtest/gtest.h>
+#include <linux/input.h>
+
+namespace android {
+
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// An arbitrary device id.
+static const int32_t DEVICE_ID = 1;
+
+// An arbitrary injector pid / uid pair that has permission to inject events.
+static const int32_t INJECTOR_PID = 999;
+static const int32_t INJECTOR_UID = 1001;
+
+
+// --- FakeInputDispatcherPolicy ---
+
+class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
+protected:
+    virtual ~FakeInputDispatcherPolicy() {
+    }
+
+public:
+    FakeInputDispatcherPolicy() {
+    }
+
+private:
+    virtual void notifyConfigurationChanged(nsecs_t when) {
+    }
+
+    virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+            const sp<InputChannel>& inputChannel) {
+        return 0;
+    }
+
+    virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) {
+    }
+
+    virtual nsecs_t getKeyRepeatTimeout() {
+        return 500 * 1000000LL;
+    }
+
+    virtual nsecs_t getKeyRepeatDelay() {
+        return 50 * 1000000LL;
+    }
+
+    virtual int32_t getMaxEventsPerSecond() {
+        return 60;
+    }
+
+    virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) {
+    }
+
+    virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
+    }
+
+    virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags) {
+        return false;
+    }
+
+    virtual bool dispatchUnhandledKey(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
+        return false;
+    }
+
+    virtual void notifySwitch(nsecs_t when,
+            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
+    }
+
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
+    }
+
+    virtual bool checkInjectEventsPermissionNonReentrant(
+            int32_t injectorPid, int32_t injectorUid) {
+        return false;
+    }
+};
+
+
+// --- InputDispatcherTest ---
+
+class InputDispatcherTest : public testing::Test {
+protected:
+    sp<FakeInputDispatcherPolicy> mFakePolicy;
+    sp<InputDispatcher> mDispatcher;
+
+    virtual void SetUp() {
+        mFakePolicy = new FakeInputDispatcherPolicy();
+        mDispatcher = new InputDispatcher(mFakePolicy);
+    }
+
+    virtual void TearDown() {
+        mFakePolicy.clear();
+        mDispatcher.clear();
+    }
+};
+
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
+    KeyEvent event;
+
+    // Rejects undefined key actions.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+            /*action*/ -1, 0,
+            AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject key events with undefined action.";
+
+    // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+            AKEY_EVENT_ACTION_MULTIPLE, 0,
+            AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject key events with ACTION_MULTIPLE.";
+}
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
+    MotionEvent event;
+    int32_t pointerIds[MAX_POINTERS + 1];
+    PointerCoords pointerCoords[MAX_POINTERS + 1];
+    for (int i = 0; i <= MAX_POINTERS; i++) {
+        pointerIds[i] = i;
+    }
+
+    // Rejects undefined motion actions.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            /*action*/ -1, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with undefined action.";
+
+    // Rejects pointer down with invalid index.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer down index too large.";
+
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_DOWN | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer down index too small.";
+
+    // Rejects pointer up with invalid index.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer up index too large.";
+
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_POINTER_UP | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer up index too small.";
+
+    // Rejects motion events with invalid number of pointers.
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 0, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with 0 pointers.";
+
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with more than MAX_POINTERS pointers.";
+
+    // Rejects motion events with invalid pointer ids.
+    pointerIds[0] = -1;
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer ids less than 0.";
+
+    pointerIds[0] = MAX_POINTER_ID + 1;
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 1, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
+
+    // Rejects motion events with duplicate pointer ids.
+    pointerIds[0] = 1;
+    pointerIds[1] = 1;
+    event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+            AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+            ARBITRARY_TIME, ARBITRARY_TIME,
+            /*pointerCount*/ 2, pointerIds, pointerCoords);
+    ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            << "Should reject motion events with duplicate pointer ids.";
+}
+
+} // namespace android
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
new file mode 100644
index 0000000..9d2c52f
--- /dev/null
+++ b/services/input/tests/InputReader_test.cpp
@@ -0,0 +1,3643 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include "../InputReader.h"
+
+#include <utils/List.h>
+#include <gtest/gtest.h>
+#include <math.h>
+
+namespace android {
+
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// Arbitrary display properties.
+static const int32_t DISPLAY_ID = 0;
+static const int32_t DISPLAY_WIDTH = 480;
+static const int32_t DISPLAY_HEIGHT = 800;
+
+// Error tolerance for floating point assertions.
+static const float EPSILON = 0.001f;
+
+template<typename T>
+static inline T min(T a, T b) {
+    return a < b ? a : b;
+}
+
+static inline float avg(float x, float y) {
+    return (x + y) / 2;
+}
+
+
+// --- FakePointerController ---
+
+class FakePointerController : public PointerControllerInterface {
+    bool mHaveBounds;
+    float mMinX, mMinY, mMaxX, mMaxY;
+
+protected:
+    virtual ~FakePointerController() { }
+
+public:
+    FakePointerController() :
+        mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0) {
+    }
+
+    void setBounds(float minX, float minY, float maxX, float maxY) {
+        mHaveBounds = true;
+        mMinX = minX;
+        mMinY = minY;
+        mMaxX = maxX;
+        mMaxY = maxY;
+    }
+
+private:
+    virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const {
+        *outMinX = mMinX;
+        *outMinY = mMinY;
+        *outMaxX = mMaxX;
+        *outMaxY = mMaxY;
+        return mHaveBounds;
+    }
+
+    virtual void move(float deltaX, float deltaY) {
+    }
+
+    virtual void setButtonState(uint32_t buttonState) {
+    }
+
+    virtual uint32_t getButtonState() const {
+        return 0;
+    }
+
+    virtual void setPosition(float x, float y) {
+    }
+
+    virtual void getPosition(float* outX, float* outY) const {
+        *outX = 0;
+        *outY = 0;
+    }
+};
+
+
+// --- FakeInputReaderPolicy ---
+
+class FakeInputReaderPolicy : public InputReaderPolicyInterface {
+    struct DisplayInfo {
+        int32_t width;
+        int32_t height;
+        int32_t orientation;
+    };
+
+    KeyedVector<int32_t, DisplayInfo> mDisplayInfos;
+    bool mFilterTouchEvents;
+    bool mFilterJumpyTouchEvents;
+    Vector<String8> mExcludedDeviceNames;
+    KeyedVector<int32_t, sp<FakePointerController> > mPointerControllers;
+
+protected:
+    virtual ~FakeInputReaderPolicy() { }
+
+public:
+    FakeInputReaderPolicy() :
+            mFilterTouchEvents(false), mFilterJumpyTouchEvents(false) {
+    }
+
+    void removeDisplayInfo(int32_t displayId) {
+        mDisplayInfos.removeItem(displayId);
+    }
+
+    void setDisplayInfo(int32_t displayId, int32_t width, int32_t height, int32_t orientation) {
+        removeDisplayInfo(displayId);
+
+        DisplayInfo info;
+        info.width = width;
+        info.height = height;
+        info.orientation = orientation;
+        mDisplayInfos.add(displayId, info);
+    }
+
+    void setFilterTouchEvents(bool enabled) {
+        mFilterTouchEvents = enabled;
+    }
+
+    void setFilterJumpyTouchEvents(bool enabled) {
+        mFilterJumpyTouchEvents = enabled;
+    }
+
+    void addExcludedDeviceName(const String8& deviceName) {
+        mExcludedDeviceNames.push(deviceName);
+    }
+
+    void setPointerController(int32_t deviceId, const sp<FakePointerController>& controller) {
+        mPointerControllers.add(deviceId, controller);
+    }
+
+private:
+    virtual bool getDisplayInfo(int32_t displayId,
+            int32_t* width, int32_t* height, int32_t* orientation) {
+        ssize_t index = mDisplayInfos.indexOfKey(displayId);
+        if (index >= 0) {
+            const DisplayInfo& info = mDisplayInfos.valueAt(index);
+            if (width) {
+                *width = info.width;
+            }
+            if (height) {
+                *height = info.height;
+            }
+            if (orientation) {
+                *orientation = info.orientation;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    virtual bool filterTouchEvents() {
+        return mFilterTouchEvents;
+    }
+
+    virtual bool filterJumpyTouchEvents() {
+        return mFilterJumpyTouchEvents;
+    }
+
+    virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
+        outExcludedDeviceNames.appendVector(mExcludedDeviceNames);
+    }
+
+    virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
+        return mPointerControllers.valueFor(deviceId);
+    }
+};
+
+
+// --- FakeInputDispatcher ---
+
+class FakeInputDispatcher : public InputDispatcherInterface {
+public:
+    struct NotifyConfigurationChangedArgs {
+        nsecs_t eventTime;
+    };
+
+    struct NotifyKeyArgs {
+        nsecs_t eventTime;
+        int32_t deviceId;
+        int32_t source;
+        uint32_t policyFlags;
+        int32_t action;
+        int32_t flags;
+        int32_t keyCode;
+        int32_t scanCode;
+        int32_t metaState;
+        nsecs_t downTime;
+    };
+
+    struct NotifyMotionArgs {
+        nsecs_t eventTime;
+        int32_t deviceId;
+        int32_t source;
+        uint32_t policyFlags;
+        int32_t action;
+        int32_t flags;
+        int32_t metaState;
+        int32_t edgeFlags;
+        uint32_t pointerCount;
+        Vector<int32_t> pointerIds;
+        Vector<PointerCoords> pointerCoords;
+        float xPrecision;
+        float yPrecision;
+        nsecs_t downTime;
+    };
+
+    struct NotifySwitchArgs {
+        nsecs_t when;
+        int32_t switchCode;
+        int32_t switchValue;
+        uint32_t policyFlags;
+    };
+
+private:
+    List<NotifyConfigurationChangedArgs> mNotifyConfigurationChangedArgs;
+    List<NotifyKeyArgs> mNotifyKeyArgs;
+    List<NotifyMotionArgs> mNotifyMotionArgs;
+    List<NotifySwitchArgs> mNotifySwitchArgs;
+
+protected:
+    virtual ~FakeInputDispatcher() { }
+
+public:
+    FakeInputDispatcher() {
+    }
+
+    void assertNotifyConfigurationChangedWasCalled(NotifyConfigurationChangedArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifyConfigurationChangedArgs.empty())
+                << "Expected notifyConfigurationChanged() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifyConfigurationChangedArgs.begin();
+        }
+        mNotifyConfigurationChangedArgs.erase(mNotifyConfigurationChangedArgs.begin());
+    }
+
+    void assertNotifyKeyWasCalled(NotifyKeyArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifyKeyArgs.empty())
+                << "Expected notifyKey() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifyKeyArgs.begin();
+        }
+        mNotifyKeyArgs.erase(mNotifyKeyArgs.begin());
+    }
+
+    void assertNotifyKeyWasNotCalled() {
+        ASSERT_TRUE(mNotifyKeyArgs.empty())
+                << "Expected notifyKey() to not have been called.";
+    }
+
+    void assertNotifyMotionWasCalled(NotifyMotionArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifyMotionArgs.empty())
+                << "Expected notifyMotion() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifyMotionArgs.begin();
+        }
+        mNotifyMotionArgs.erase(mNotifyMotionArgs.begin());
+    }
+
+    void assertNotifyMotionWasNotCalled() {
+        ASSERT_TRUE(mNotifyMotionArgs.empty())
+                << "Expected notifyMotion() to not have been called.";
+    }
+
+    void assertNotifySwitchWasCalled(NotifySwitchArgs* outArgs = NULL) {
+        ASSERT_FALSE(mNotifySwitchArgs.empty())
+                << "Expected notifySwitch() to have been called.";
+        if (outArgs) {
+            *outArgs = *mNotifySwitchArgs.begin();
+        }
+        mNotifySwitchArgs.erase(mNotifySwitchArgs.begin());
+    }
+
+private:
+    virtual void notifyConfigurationChanged(nsecs_t eventTime) {
+        NotifyConfigurationChangedArgs args;
+        args.eventTime = eventTime;
+        mNotifyConfigurationChangedArgs.push_back(args);
+    }
+
+    virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+            int32_t scanCode, int32_t metaState, nsecs_t downTime) {
+        NotifyKeyArgs args;
+        args.eventTime = eventTime;
+        args.deviceId = deviceId;
+        args.source = source;
+        args.policyFlags = policyFlags;
+        args.action = action;
+        args.flags = flags;
+        args.keyCode = keyCode;
+        args.scanCode = scanCode;
+        args.metaState = metaState;
+        args.downTime = downTime;
+        mNotifyKeyArgs.push_back(args);
+    }
+
+    virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
+            uint32_t policyFlags, int32_t action, int32_t flags,
+            int32_t metaState, int32_t edgeFlags,
+            uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+            float xPrecision, float yPrecision, nsecs_t downTime) {
+        NotifyMotionArgs args;
+        args.eventTime = eventTime;
+        args.deviceId = deviceId;
+        args.source = source;
+        args.policyFlags = policyFlags;
+        args.action = action;
+        args.flags = flags;
+        args.metaState = metaState;
+        args.edgeFlags = edgeFlags;
+        args.pointerCount = pointerCount;
+        args.pointerIds.clear();
+        args.pointerIds.appendArray(pointerIds, pointerCount);
+        args.pointerCoords.clear();
+        args.pointerCoords.appendArray(pointerCoords, pointerCount);
+        args.xPrecision = xPrecision;
+        args.yPrecision = yPrecision;
+        args.downTime = downTime;
+        mNotifyMotionArgs.push_back(args);
+    }
+
+    virtual void notifySwitch(nsecs_t when,
+            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
+        NotifySwitchArgs args;
+        args.when = when;
+        args.switchCode = switchCode;
+        args.switchValue = switchValue;
+        args.policyFlags = policyFlags;
+        mNotifySwitchArgs.push_back(args);
+    }
+
+    virtual void dump(String8& dump) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual void dispatchOnce() {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual int32_t injectInputEvent(const InputEvent* event,
+            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return INPUT_EVENT_INJECTION_FAILED;
+    }
+
+    virtual void setInputWindows(const Vector<InputWindow>& inputWindows) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual void setFocusedApplication(const InputApplication* inputApplication) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual void setInputDispatchMode(bool enabled, bool frozen) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
+    virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+            const sp<InputChannel>& toChannel) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return 0;
+    }
+
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return 0;
+    }
+
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+        return 0;
+    }
+};
+
+
+// --- FakeEventHub ---
+
+class FakeEventHub : public EventHubInterface {
+    struct KeyInfo {
+        int32_t keyCode;
+        uint32_t flags;
+    };
+
+    struct Device {
+        String8 name;
+        uint32_t classes;
+        PropertyMap configuration;
+        KeyedVector<int, RawAbsoluteAxisInfo> axes;
+        KeyedVector<int32_t, int32_t> keyCodeStates;
+        KeyedVector<int32_t, int32_t> scanCodeStates;
+        KeyedVector<int32_t, int32_t> switchStates;
+        KeyedVector<int32_t, KeyInfo> keys;
+        KeyedVector<int32_t, bool> leds;
+        Vector<VirtualKeyDefinition> virtualKeys;
+
+        Device(const String8& name, uint32_t classes) :
+                name(name), classes(classes) {
+        }
+    };
+
+    KeyedVector<int32_t, Device*> mDevices;
+    Vector<String8> mExcludedDevices;
+    List<RawEvent> mEvents;
+
+protected:
+    virtual ~FakeEventHub() {
+        for (size_t i = 0; i < mDevices.size(); i++) {
+            delete mDevices.valueAt(i);
+        }
+    }
+
+public:
+    FakeEventHub() { }
+
+    void addDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+        Device* device = new Device(name, classes);
+        mDevices.add(deviceId, device);
+
+        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0, 0, 0);
+    }
+
+    void removeDevice(int32_t deviceId) {
+        delete mDevices.valueFor(deviceId);
+        mDevices.removeItem(deviceId);
+
+        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0, 0, 0);
+    }
+
+    void finishDeviceScan() {
+        enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0, 0, 0);
+    }
+
+    void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
+        Device* device = getDevice(deviceId);
+        device->configuration.addProperty(key, value);
+    }
+
+    void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) {
+        Device* device = getDevice(deviceId);
+        device->configuration.addAll(configuration);
+    }
+
+    void addAxis(int32_t deviceId, int axis,
+            int32_t minValue, int32_t maxValue, int flat, int fuzz) {
+        Device* device = getDevice(deviceId);
+
+        RawAbsoluteAxisInfo info;
+        info.valid = true;
+        info.minValue = minValue;
+        info.maxValue = maxValue;
+        info.flat = flat;
+        info.fuzz = fuzz;
+        device->axes.add(axis, info);
+    }
+
+    void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
+        Device* device = getDevice(deviceId);
+        device->keyCodeStates.replaceValueFor(keyCode, state);
+    }
+
+    void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
+        Device* device = getDevice(deviceId);
+        device->scanCodeStates.replaceValueFor(scanCode, state);
+    }
+
+    void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
+        Device* device = getDevice(deviceId);
+        device->switchStates.replaceValueFor(switchCode, state);
+    }
+
+    void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) {
+        Device* device = getDevice(deviceId);
+        KeyInfo info;
+        info.keyCode = keyCode;
+        info.flags = flags;
+        device->keys.add(scanCode, info);
+    }
+
+    void addLed(int32_t deviceId, int32_t led, bool initialState) {
+        Device* device = getDevice(deviceId);
+        device->leds.add(led, initialState);
+    }
+
+    bool getLedState(int32_t deviceId, int32_t led) {
+        Device* device = getDevice(deviceId);
+        return device->leds.valueFor(led);
+    }
+
+    Vector<String8>& getExcludedDevices() {
+        return mExcludedDevices;
+    }
+
+    void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) {
+        Device* device = getDevice(deviceId);
+        device->virtualKeys.push(definition);
+    }
+
+    void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type,
+            int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+        RawEvent event;
+        event.when = when;
+        event.deviceId = deviceId;
+        event.type = type;
+        event.scanCode = scanCode;
+        event.keyCode = keyCode;
+        event.value = value;
+        event.flags = flags;
+        mEvents.push_back(event);
+    }
+
+    void assertQueueIsEmpty() {
+        ASSERT_EQ(size_t(0), mEvents.size())
+                << "Expected the event queue to be empty (fully consumed).";
+    }
+
+private:
+    Device* getDevice(int32_t deviceId) const {
+        ssize_t index = mDevices.indexOfKey(deviceId);
+        return index >= 0 ? mDevices.valueAt(index) : NULL;
+    }
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const {
+        Device* device = getDevice(deviceId);
+        return device ? device->classes : 0;
+    }
+
+    virtual String8 getDeviceName(int32_t deviceId) const {
+        Device* device = getDevice(deviceId);
+        return device ? device->name : String8("unknown");
+    }
+
+    virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            *outConfiguration = device->configuration;
+        }
+    }
+
+    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+            RawAbsoluteAxisInfo* outAxisInfo) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->axes.indexOfKey(axis);
+            if (index >= 0) {
+                *outAxisInfo = device->axes.valueAt(index);
+                return OK;
+            }
+        }
+        return -1;
+    }
+
+    virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
+            int32_t* outKeycode, uint32_t* outFlags) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->keys.indexOfKey(scancode);
+            if (index >= 0) {
+                if (outKeycode) {
+                    *outKeycode = device->keys.valueAt(index).keyCode;
+                }
+                if (outFlags) {
+                    *outFlags = device->keys.valueAt(index).flags;
+                }
+                return OK;
+            }
+        }
+        return NAME_NOT_FOUND;
+    }
+
+    virtual void addExcludedDevice(const char* deviceName) {
+        mExcludedDevices.add(String8(deviceName));
+    }
+
+    virtual bool getEvent(RawEvent* outEvent) {
+        if (mEvents.empty()) {
+            return false;
+        }
+
+        *outEvent = *mEvents.begin();
+        mEvents.erase(mEvents.begin());
+        return true;
+    }
+
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->scanCodeStates.indexOfKey(scanCode);
+            if (index >= 0) {
+                return device->scanCodeStates.valueAt(index);
+            }
+        }
+        return AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->keyCodeStates.indexOfKey(keyCode);
+            if (index >= 0) {
+                return device->keyCodeStates.valueAt(index);
+            }
+        }
+        return AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->switchStates.indexOfKey(sw);
+            if (index >= 0) {
+                return device->switchStates.valueAt(index);
+            }
+        }
+        return AKEY_STATE_UNKNOWN;
+    }
+
+    virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+            uint8_t* outFlags) const {
+        bool result = false;
+        Device* device = getDevice(deviceId);
+        if (device) {
+            for (size_t i = 0; i < numCodes; i++) {
+                for (size_t j = 0; j < device->keys.size(); j++) {
+                    if (keyCodes[i] == device->keys.valueAt(j).keyCode) {
+                        outFlags[i] = 1;
+                        result = true;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    virtual bool hasLed(int32_t deviceId, int32_t led) const {
+        Device* device = getDevice(deviceId);
+        return device && device->leds.indexOfKey(led) >= 0;
+    }
+
+    virtual void setLedState(int32_t deviceId, int32_t led, bool on) {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->leds.indexOfKey(led);
+            if (index >= 0) {
+                device->leds.replaceValueAt(led, on);
+            } else {
+                ADD_FAILURE()
+                        << "Attempted to set the state of an LED that the EventHub declared "
+                        "was not present.  led=" << led;
+            }
+        }
+    }
+
+    virtual void getVirtualKeyDefinitions(int32_t deviceId,
+            Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+        outVirtualKeys.clear();
+
+        Device* device = getDevice(deviceId);
+        if (device) {
+            outVirtualKeys.appendVector(device->virtualKeys);
+        }
+    }
+
+    virtual void dump(String8& dump) {
+    }
+};
+
+
+// --- FakeInputReaderContext ---
+
+class FakeInputReaderContext : public InputReaderContext {
+    sp<EventHubInterface> mEventHub;
+    sp<InputReaderPolicyInterface> mPolicy;
+    sp<InputDispatcherInterface> mDispatcher;
+    int32_t mGlobalMetaState;
+    bool mUpdateGlobalMetaStateWasCalled;
+
+public:
+    FakeInputReaderContext(const sp<EventHubInterface>& eventHub,
+            const sp<InputReaderPolicyInterface>& policy,
+            const sp<InputDispatcherInterface>& dispatcher) :
+            mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
+            mGlobalMetaState(0) {
+    }
+
+    virtual ~FakeInputReaderContext() { }
+
+    void assertUpdateGlobalMetaStateWasCalled() {
+        ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled)
+                << "Expected updateGlobalMetaState() to have been called.";
+        mUpdateGlobalMetaStateWasCalled = false;
+    }
+
+    void setGlobalMetaState(int32_t state) {
+        mGlobalMetaState = state;
+    }
+
+private:
+    virtual void updateGlobalMetaState() {
+        mUpdateGlobalMetaStateWasCalled = true;
+    }
+
+    virtual int32_t getGlobalMetaState() {
+        return mGlobalMetaState;
+    }
+
+    virtual EventHubInterface* getEventHub() {
+        return mEventHub.get();
+    }
+
+    virtual InputReaderPolicyInterface* getPolicy() {
+        return mPolicy.get();
+    }
+
+    virtual InputDispatcherInterface* getDispatcher() {
+        return mDispatcher.get();
+    }
+};
+
+
+// --- FakeInputMapper ---
+
+class FakeInputMapper : public InputMapper {
+    uint32_t mSources;
+    int32_t mKeyboardType;
+    int32_t mMetaState;
+    KeyedVector<int32_t, int32_t> mKeyCodeStates;
+    KeyedVector<int32_t, int32_t> mScanCodeStates;
+    KeyedVector<int32_t, int32_t> mSwitchStates;
+    Vector<int32_t> mSupportedKeyCodes;
+    RawEvent mLastEvent;
+
+    bool mConfigureWasCalled;
+    bool mResetWasCalled;
+    bool mProcessWasCalled;
+
+public:
+    FakeInputMapper(InputDevice* device, uint32_t sources) :
+            InputMapper(device),
+            mSources(sources), mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE),
+            mMetaState(0),
+            mConfigureWasCalled(false), mResetWasCalled(false), mProcessWasCalled(false) {
+    }
+
+    virtual ~FakeInputMapper() { }
+
+    void setKeyboardType(int32_t keyboardType) {
+        mKeyboardType = keyboardType;
+    }
+
+    void setMetaState(int32_t metaState) {
+        mMetaState = metaState;
+    }
+
+    void assertConfigureWasCalled() {
+        ASSERT_TRUE(mConfigureWasCalled)
+                << "Expected configure() to have been called.";
+        mConfigureWasCalled = false;
+    }
+
+    void assertResetWasCalled() {
+        ASSERT_TRUE(mResetWasCalled)
+                << "Expected reset() to have been called.";
+        mResetWasCalled = false;
+    }
+
+    void assertProcessWasCalled(RawEvent* outLastEvent = NULL) {
+        ASSERT_TRUE(mProcessWasCalled)
+                << "Expected process() to have been called.";
+        if (outLastEvent) {
+            *outLastEvent = mLastEvent;
+        }
+        mProcessWasCalled = false;
+    }
+
+    void setKeyCodeState(int32_t keyCode, int32_t state) {
+        mKeyCodeStates.replaceValueFor(keyCode, state);
+    }
+
+    void setScanCodeState(int32_t scanCode, int32_t state) {
+        mScanCodeStates.replaceValueFor(scanCode, state);
+    }
+
+    void setSwitchState(int32_t switchCode, int32_t state) {
+        mSwitchStates.replaceValueFor(switchCode, state);
+    }
+
+    void addSupportedKeyCode(int32_t keyCode) {
+        mSupportedKeyCodes.add(keyCode);
+    }
+
+private:
+    virtual uint32_t getSources() {
+        return mSources;
+    }
+
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) {
+        InputMapper::populateDeviceInfo(deviceInfo);
+
+        if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) {
+            deviceInfo->setKeyboardType(mKeyboardType);
+        }
+    }
+
+    virtual void configure() {
+        mConfigureWasCalled = true;
+    }
+
+    virtual void reset() {
+        mResetWasCalled = true;
+    }
+
+    virtual void process(const RawEvent* rawEvent) {
+        mLastEvent = *rawEvent;
+        mProcessWasCalled = true;
+    }
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+        ssize_t index = mKeyCodeStates.indexOfKey(keyCode);
+        return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+        ssize_t index = mScanCodeStates.indexOfKey(scanCode);
+        return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+    }
+
+    virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+        ssize_t index = mSwitchStates.indexOfKey(switchCode);
+        return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+    }
+
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags) {
+        bool result = false;
+        for (size_t i = 0; i < numCodes; i++) {
+            for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) {
+                if (keyCodes[i] == mSupportedKeyCodes[j]) {
+                    outFlags[i] = 1;
+                    result = true;
+                }
+            }
+        }
+        return result;
+    }
+
+    virtual int32_t getMetaState() {
+        return mMetaState;
+    }
+};
+
+
+// --- InstrumentedInputReader ---
+
+class InstrumentedInputReader : public InputReader {
+    InputDevice* mNextDevice;
+
+public:
+    InstrumentedInputReader(const sp<EventHubInterface>& eventHub,
+            const sp<InputReaderPolicyInterface>& policy,
+            const sp<InputDispatcherInterface>& dispatcher) :
+            InputReader(eventHub, policy, dispatcher) {
+    }
+
+    virtual ~InstrumentedInputReader() {
+        if (mNextDevice) {
+            delete mNextDevice;
+        }
+    }
+
+    void setNextDevice(InputDevice* device) {
+        mNextDevice = device;
+    }
+
+protected:
+    virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+        if (mNextDevice) {
+            InputDevice* device = mNextDevice;
+            mNextDevice = NULL;
+            return device;
+        }
+        return InputReader::createDevice(deviceId, name, classes);
+    }
+
+    friend class InputReaderTest;
+};
+
+
+// --- InputReaderTest ---
+
+class InputReaderTest : public testing::Test {
+protected:
+    sp<FakeInputDispatcher> mFakeDispatcher;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    sp<FakeEventHub> mFakeEventHub;
+    sp<InstrumentedInputReader> mReader;
+
+    virtual void SetUp() {
+        mFakeEventHub = new FakeEventHub();
+        mFakePolicy = new FakeInputReaderPolicy();
+        mFakeDispatcher = new FakeInputDispatcher();
+
+        mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+    }
+
+    virtual void TearDown() {
+        mReader.clear();
+
+        mFakeDispatcher.clear();
+        mFakePolicy.clear();
+        mFakeEventHub.clear();
+    }
+
+    void addDevice(int32_t deviceId, const String8& name, uint32_t classes,
+            const PropertyMap* configuration) {
+        mFakeEventHub->addDevice(deviceId, name, classes);
+        if (configuration) {
+            mFakeEventHub->addConfigurationMap(deviceId, configuration);
+        }
+        mFakeEventHub->finishDeviceScan();
+        mReader->loopOnce();
+        mReader->loopOnce();
+        mFakeEventHub->assertQueueIsEmpty();
+    }
+
+    FakeInputMapper* addDeviceWithFakeInputMapper(int32_t deviceId,
+            const String8& name, uint32_t classes, uint32_t sources,
+            const PropertyMap* configuration) {
+        InputDevice* device = new InputDevice(mReader.get(), deviceId, name);
+        FakeInputMapper* mapper = new FakeInputMapper(device, sources);
+        device->addMapper(mapper);
+        mReader->setNextDevice(device);
+        addDevice(deviceId, name, classes, configuration);
+        return mapper;
+    }
+};
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenNoDevices_ReturnsDefaults) {
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenAlphabeticKeyboardPresent_ReturnsQwertyKeyboard) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("keyboard"),
+            INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_QWERTY, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenTouchScreenPresent_ReturnsFingerTouchScreen) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("touchscreen"),
+            INPUT_DEVICE_CLASS_TOUCHSCREEN, NULL));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_FINGER, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenMousePresent_ReturnsNoNavigation) {
+    sp<FakePointerController> controller = new FakePointerController();
+    mFakePolicy->setPointerController(0, controller);
+
+    PropertyMap configuration;
+    configuration.addProperty(String8("cursor.mode"), String8("pointer"));
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("mouse"),
+            INPUT_DEVICE_CLASS_CURSOR, &configuration));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenTrackballPresent_ReturnsTrackballNavigation) {
+    PropertyMap configuration;
+    configuration.addProperty(String8("cursor.mode"), String8("navigation"));
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("trackball"),
+            INPUT_DEVICE_CLASS_CURSOR, &configuration));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_TRACKBALL, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenDPadPresent_ReturnsDPadNavigation) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("dpad"),
+            INPUT_DEVICE_CLASS_DPAD, NULL));
+
+    InputConfiguration config;
+    mReader->getInputConfiguration(&config);
+
+    ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+    ASSERT_EQ(InputConfiguration::NAVIGATION_DPAD, config.navigation);
+    ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsValid) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
+            INPUT_DEVICE_CLASS_KEYBOARD, NULL));
+
+    InputDeviceInfo info;
+    status_t result = mReader->getInputDeviceInfo(1, &info);
+
+    ASSERT_EQ(OK, result);
+    ASSERT_EQ(1, info.getId());
+    ASSERT_STREQ("keyboard", info.getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, info.getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, info.getSources());
+    ASSERT_EQ(size_t(0), info.getMotionRanges().size());
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsInvalid) {
+    InputDeviceInfo info;
+    status_t result = mReader->getInputDeviceInfo(-1, &info);
+
+    ASSERT_EQ(NAME_NOT_FOUND, result);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsIgnored) {
+    addDevice(1, String8("ignored"), 0, NULL); // no classes so device will be ignored
+
+    InputDeviceInfo info;
+    status_t result = mReader->getInputDeviceInfo(1, &info);
+
+    ASSERT_EQ(NAME_NOT_FOUND, result);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceIds) {
+    sp<FakePointerController> controller = new FakePointerController();
+    mFakePolicy->setPointerController(2, controller);
+
+    ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
+            INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL));
+    ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("mouse"),
+            INPUT_DEVICE_CLASS_CURSOR, NULL));
+
+    Vector<int32_t> ids;
+    mReader->getInputDeviceIds(ids);
+
+    ASSERT_EQ(size_t(2), ids.size());
+    ASSERT_EQ(1, ids[0]);
+    ASSERT_EQ(2, ids[1]);
+}
+
+TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+    mapper->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0,
+            AINPUT_SOURCE_ANY, AKEYCODE_A))
+            << "Should return unknown when the device id is >= 0 but unknown.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(1,
+            AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1,
+            AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+    mapper->setScanCodeState(KEY_A, AKEY_STATE_DOWN);
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0,
+            AINPUT_SOURCE_ANY, KEY_A))
+            << "Should return unknown when the device id is >= 0 but unknown.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(1,
+            AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1,
+            AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+    mapper->setSwitchState(SW_LID, AKEY_STATE_DOWN);
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0,
+            AINPUT_SOURCE_ANY, SW_LID))
+            << "Should return unknown when the device id is >= 0 but unknown.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(1,
+            AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1,
+            AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1,
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+    mapper->addSupportedKeyCode(AKEYCODE_A);
+    mapper->addSupportedKeyCode(AKEYCODE_B);
+
+    const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+    uint8_t flags[4] = { 0, 0, 0, 1 };
+
+    ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags))
+            << "Should return false when device id is >= 0 but unknown.";
+    ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_FALSE(mReader->hasKeys(1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return false when device id is valid but the sources are not supported by the device.";
+    ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_TRUE(mReader->hasKeys(1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+    ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return false when the device id is < 0 but the sources are not supported by any device.";
+    ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+    flags[3] = 1;
+    ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+    ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+}
+
+TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) {
+    addDevice(1, String8("ignored"), INPUT_DEVICE_CLASS_KEYBOARD, NULL);
+
+    FakeInputDispatcher::NotifyConfigurationChangedArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyConfigurationChangedWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+}
+
+TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
+    FakeInputMapper* mapper = NULL;
+    ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+            INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+
+    mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, AKEYCODE_A, 1, POLICY_FLAG_WAKE);
+    mReader->loopOnce();
+    ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
+
+    RawEvent event;
+    ASSERT_NO_FATAL_FAILURE(mapper->assertProcessWasCalled(&event));
+    ASSERT_EQ(0, event.when);
+    ASSERT_EQ(1, event.deviceId);
+    ASSERT_EQ(EV_KEY, event.type);
+    ASSERT_EQ(KEY_A, event.scanCode);
+    ASSERT_EQ(AKEYCODE_A, event.keyCode);
+    ASSERT_EQ(1, event.value);
+    ASSERT_EQ(POLICY_FLAG_WAKE, event.flags);
+}
+
+
+// --- InputDeviceTest ---
+
+class InputDeviceTest : public testing::Test {
+protected:
+    static const char* DEVICE_NAME;
+    static const int32_t DEVICE_ID;
+
+    sp<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    sp<FakeInputDispatcher> mFakeDispatcher;
+    FakeInputReaderContext* mFakeContext;
+
+    InputDevice* mDevice;
+
+    virtual void SetUp() {
+        mFakeEventHub = new FakeEventHub();
+        mFakePolicy = new FakeInputReaderPolicy();
+        mFakeDispatcher = new FakeInputDispatcher();
+        mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+
+        mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+    }
+
+    virtual void TearDown() {
+        delete mDevice;
+
+        delete mFakeContext;
+        mFakeDispatcher.clear();
+        mFakePolicy.clear();
+        mFakeEventHub.clear();
+    }
+};
+
+const char* InputDeviceTest::DEVICE_NAME = "device";
+const int32_t InputDeviceTest::DEVICE_ID = 1;
+
+TEST_F(InputDeviceTest, ImmutableProperties) {
+    ASSERT_EQ(DEVICE_ID, mDevice->getId());
+    ASSERT_STREQ(DEVICE_NAME, mDevice->getName());
+}
+
+TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
+    // Configuration.
+    mDevice->configure();
+
+    // Metadata.
+    ASSERT_TRUE(mDevice->isIgnored());
+    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources());
+
+    InputDeviceInfo info;
+    mDevice->getDeviceInfo(&info);
+    ASSERT_EQ(DEVICE_ID, info.getId());
+    ASSERT_STREQ(DEVICE_NAME, info.getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NONE, info.getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, info.getSources());
+
+    // State queries.
+    ASSERT_EQ(0, mDevice->getMetaState());
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+            << "Ignored device should return unknown key code state.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+            << "Ignored device should return unknown scan code state.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0))
+            << "Ignored device should return unknown switch state.";
+
+    const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+    uint8_t flags[2] = { 0, 1 };
+    ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags))
+            << "Ignored device should never mark any key codes.";
+    ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged.";
+    ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged.";
+
+    // Reset.
+    mDevice->reset();
+}
+
+TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) {
+    // Configuration.
+    mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8("key"), String8("value"));
+
+    FakeInputMapper* mapper1 = new FakeInputMapper(mDevice, AINPUT_SOURCE_KEYBOARD);
+    mapper1->setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    mapper1->setMetaState(AMETA_ALT_ON);
+    mapper1->addSupportedKeyCode(AKEYCODE_A);
+    mapper1->addSupportedKeyCode(AKEYCODE_B);
+    mapper1->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+    mapper1->setKeyCodeState(AKEYCODE_B, AKEY_STATE_UP);
+    mapper1->setScanCodeState(2, AKEY_STATE_DOWN);
+    mapper1->setScanCodeState(3, AKEY_STATE_UP);
+    mapper1->setSwitchState(4, AKEY_STATE_DOWN);
+    mDevice->addMapper(mapper1);
+
+    FakeInputMapper* mapper2 = new FakeInputMapper(mDevice, AINPUT_SOURCE_TOUCHSCREEN);
+    mapper2->setMetaState(AMETA_SHIFT_ON);
+    mDevice->addMapper(mapper2);
+
+    mDevice->configure();
+
+    String8 propertyValue;
+    ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty(String8("key"), propertyValue))
+            << "Device should have read configuration during configuration phase.";
+    ASSERT_STREQ("value", propertyValue.string());
+
+    ASSERT_NO_FATAL_FAILURE(mapper1->assertConfigureWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mapper2->assertConfigureWasCalled());
+
+    // Metadata.
+    ASSERT_FALSE(mDevice->isIgnored());
+    ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), mDevice->getSources());
+
+    InputDeviceInfo info;
+    mDevice->getDeviceInfo(&info);
+    ASSERT_EQ(DEVICE_ID, info.getId());
+    ASSERT_STREQ(DEVICE_NAME, info.getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, info.getKeyboardType());
+    ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), info.getSources());
+
+    // State queries.
+    ASSERT_EQ(AMETA_ALT_ON | AMETA_SHIFT_ON, mDevice->getMetaState())
+            << "Should query mappers and combine meta states.";
+
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown key code state when source not supported.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown scan code state when source not supported.";
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+            << "Should return unknown switch state when source not supported.";
+
+    ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, AKEYCODE_A))
+            << "Should query mapper when source is supported.";
+    ASSERT_EQ(AKEY_STATE_UP, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 3))
+            << "Should query mapper when source is supported.";
+    ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4))
+            << "Should query mapper when source is supported.";
+
+    const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+    uint8_t flags[4] = { 0, 0, 0, 1 };
+    ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+            << "Should do nothing when source is unsupported.";
+    ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported.";
+    ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported.";
+    ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported.";
+    ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported.";
+
+    ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags))
+            << "Should query mapper when source is supported.";
+    ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set.";
+    ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set.";
+    ASSERT_EQ(0, flags[2]) << "Flag for unsupported key should be unchanged.";
+    ASSERT_EQ(1, flags[3]) << "Flag for unsupported key should be unchanged.";
+
+    // Event handling.
+    RawEvent event;
+    mDevice->process(&event);
+
+    ASSERT_NO_FATAL_FAILURE(mapper1->assertProcessWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mapper2->assertProcessWasCalled());
+
+    // Reset.
+    mDevice->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mapper1->assertResetWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mapper2->assertResetWasCalled());
+}
+
+
+// --- InputMapperTest ---
+
+class InputMapperTest : public testing::Test {
+protected:
+    static const char* DEVICE_NAME;
+    static const int32_t DEVICE_ID;
+
+    sp<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    sp<FakeInputDispatcher> mFakeDispatcher;
+    FakeInputReaderContext* mFakeContext;
+    InputDevice* mDevice;
+
+    virtual void SetUp() {
+        mFakeEventHub = new FakeEventHub();
+        mFakePolicy = new FakeInputReaderPolicy();
+        mFakeDispatcher = new FakeInputDispatcher();
+        mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+
+        mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
+    }
+
+    virtual void TearDown() {
+        delete mDevice;
+        delete mFakeContext;
+        mFakeDispatcher.clear();
+        mFakePolicy.clear();
+        mFakeEventHub.clear();
+    }
+
+    void addConfigurationProperty(const char* key, const char* value) {
+        mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8(key), String8(value));
+    }
+
+    void addMapperAndConfigure(InputMapper* mapper) {
+        mDevice->addMapper(mapper);
+        mDevice->configure();
+    }
+
+    static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type,
+            int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+        RawEvent event;
+        event.when = when;
+        event.deviceId = deviceId;
+        event.type = type;
+        event.scanCode = scanCode;
+        event.keyCode = keyCode;
+        event.value = value;
+        event.flags = flags;
+        mapper->process(&event);
+    }
+
+    static void assertMotionRange(const InputDeviceInfo& info,
+            int32_t rangeType, float min, float max, float flat, float fuzz) {
+        const InputDeviceInfo::MotionRange* range = info.getMotionRange(rangeType);
+        ASSERT_TRUE(range != NULL) << "Range: " << rangeType;
+        ASSERT_NEAR(min, range->min, EPSILON) << "Range: " << rangeType;
+        ASSERT_NEAR(max, range->max, EPSILON) << "Range: " << rangeType;
+        ASSERT_NEAR(flat, range->flat, EPSILON) << "Range: " << rangeType;
+        ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Range: " << rangeType;
+    }
+
+    static void assertPointerCoords(const PointerCoords& coords,
+            float x, float y, float pressure, float size,
+            float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+            float orientation) {
+        ASSERT_NEAR(x, coords.x, 1);
+        ASSERT_NEAR(y, coords.y, 1);
+        ASSERT_NEAR(pressure, coords.pressure, EPSILON);
+        ASSERT_NEAR(size, coords.size, EPSILON);
+        ASSERT_NEAR(touchMajor, coords.touchMajor, 1);
+        ASSERT_NEAR(touchMinor, coords.touchMinor, 1);
+        ASSERT_NEAR(toolMajor, coords.toolMajor, 1);
+        ASSERT_NEAR(toolMinor, coords.toolMinor, 1);
+        ASSERT_NEAR(orientation, coords.orientation, EPSILON);
+    }
+};
+
+const char* InputMapperTest::DEVICE_NAME = "device";
+const int32_t InputMapperTest::DEVICE_ID = 1;
+
+
+// --- SwitchInputMapperTest ---
+
+class SwitchInputMapperTest : public InputMapperTest {
+protected:
+};
+
+TEST_F(SwitchInputMapperTest, GetSources) {
+    SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(uint32_t(0), mapper->getSources());
+}
+
+TEST_F(SwitchInputMapperTest, GetSwitchState) {
+    SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 1);
+    ASSERT_EQ(1, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+
+    mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 0);
+    ASSERT_EQ(0, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+}
+
+TEST_F(SwitchInputMapperTest, Process) {
+    SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+    addMapperAndConfigure(mapper);
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 0, 1, 0);
+
+    FakeInputDispatcher::NotifySwitchArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifySwitchWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.when);
+    ASSERT_EQ(SW_LID, args.switchCode);
+    ASSERT_EQ(1, args.switchValue);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+}
+
+
+// --- KeyboardInputMapperTest ---
+
+class KeyboardInputMapperTest : public InputMapperTest {
+protected:
+    void testDPadKeyRotation(KeyboardInputMapper* mapper,
+            int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode);
+};
+
+void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper* mapper,
+        int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode) {
+    FakeInputDispatcher::NotifyKeyArgs args;
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(originalScanCode, args.scanCode);
+    ASSERT_EQ(rotatedKeyCode, args.keyCode);
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(originalScanCode, args.scanCode);
+    ASSERT_EQ(rotatedKeyCode, args.keyCode);
+}
+
+
+TEST_F(KeyboardInputMapperTest, GetSources) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper->getSources());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key up.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreNotDown_DoesNotSynthesizeKeyUp) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Key up.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since no keys still down, should not synthesize any key ups.
+    mapper->reset();
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreDown_SynthesizesKeyUps) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Metakey down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since two keys are still down, should synthesize two key ups in reverse order.
+    mapper->reset();
+
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_A, args.keyCode);
+    ASSERT_EQ(KEY_A, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_SHIFT_LEFT, args.keyCode);
+    ASSERT_EQ(KEY_LEFTSHIFT, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime);
+
+    // And that's it.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Initial metastate.
+    ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+
+    // Metakey down.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+    ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+
+    // Key down.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+    // Key up.
+    process(mapper, ARBITRARY_TIME + 2, DEVICE_ID,
+            EV_KEY, KEY_A, AKEYCODE_A, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+    // Metakey up.
+    process(mapper, ARBITRARY_TIME + 3, DEVICE_ID,
+            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+    ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_90);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addConfigurationProperty("keyboard.orientationAware", "1");
+    addMapperAndConfigure(mapper);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_0);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_90);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_180);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_UP));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_RIGHT));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_270);
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_DOWN));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT));
+    ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+            KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_UP));
+
+    // Special case: if orientation changes while key is down, we still emit the same keycode
+    // in the key up as we did in the key down.
+    FakeInputDispatcher::NotifyKeyArgs args;
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_270);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(KEY_UP, args.scanCode);
+    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_180);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(KEY_UP, args.scanCode);
+    ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+}
+
+TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 1);
+    ASSERT_EQ(1, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+    mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 0);
+    ASSERT_EQ(0, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+}
+
+TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 1);
+    ASSERT_EQ(1, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+    mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 0);
+    ASSERT_EQ(0, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+}
+
+TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    mFakeEventHub->addKey(DEVICE_ID, KEY_A, AKEYCODE_A, 0);
+
+    const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+    uint8_t flags[2] = { 0, 0 };
+    ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 1, keyCodes, flags));
+    ASSERT_TRUE(flags[0]);
+    ASSERT_FALSE(flags[1]);
+}
+
+TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) {
+    mFakeEventHub->addLed(DEVICE_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(DEVICE_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(DEVICE_ID, LED_SCROLLL, false /*initially off*/);
+
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+            AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    // Initialization should have turned all of the lights off.
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+
+    // Toggle caps lock on.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper->getMetaState());
+
+    // Toggle num lock on.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+    ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper->getMetaState());
+
+    // Toggle caps lock off.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+    ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper->getMetaState());
+
+    // Toggle scroll lock on.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+    ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+    ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper->getMetaState());
+
+    // Toggle num lock off.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+    ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper->getMetaState());
+
+    // Toggle scroll lock off.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+    ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+}
+
+
+// --- CursorInputMapperTest ---
+
+class CursorInputMapperTest : public InputMapperTest {
+protected:
+    static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
+
+    sp<FakePointerController> mFakePointerController;
+
+    virtual void SetUp() {
+        InputMapperTest::SetUp();
+
+        mFakePointerController = new FakePointerController();
+        mFakePolicy->setPointerController(DEVICE_ID, mFakePointerController);
+    }
+
+    void testMotionRotation(CursorInputMapper* mapper,
+            int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY);
+};
+
+const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+void CursorInputMapperTest::testMotionRotation(CursorInputMapper* mapper,
+        int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY) {
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, originalX, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, originalY, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
+            float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD,
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "pointer");
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper->getSources());
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper->getSources());
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "pointer");
+    addMapperAndConfigure(mapper);
+
+    InputDeviceInfo info;
+    mapper->populateDeviceInfo(&info);
+
+    // Initially there may not be a valid motion range.
+    ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_X));
+    ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_Y));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+            0.0f, 1.0f, 0.0f, 0.0f));
+
+    // When the bounds are set, then there should be a valid motion range.
+    mFakePointerController->setBounds(1, 2, 800, 480);
+
+    InputDeviceInfo info2;
+    mapper->populateDeviceInfo(&info2);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X,
+            1, 800, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y,
+            2, 480, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+            0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    InputDeviceInfo info;
+    mapper->populateDeviceInfo(&info);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X,
+            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y,
+            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+            0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press.
+    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(0, args.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(0, args.edgeFlags);
+    ASSERT_EQ(uint32_t(1), args.pointerCount);
+    ASSERT_EQ(0, args.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Button release.  Should have same down time.
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(0, args.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(0, args.edgeFlags);
+    ASSERT_EQ(uint32_t(1), args.pointerCount);
+    ASSERT_EQ(0, args.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Motion in X but not Y.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Motion in Y but not X.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NEAR(0.0f, args.pointerCoords[0].x, EPSILON);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press without following sync.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Button release without following sync.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Combined X, Y and Button.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            1.0f / TRACKBALL_MOVEMENT_THRESHOLD, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+            1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Move X, Y a bit while pressed.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 2, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+            1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+    // Release Button.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Reset_WhenButtonIsNotDown_ShouldNotSynthesizeButtonUp) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+    // Button release.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+    // Reset.  Should not synthesize button up since button is not pressed.
+    mapper->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(CursorInputMapperTest, Reset_WhenButtonIsDown_ShouldSynthesizeButtonUp) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Button press.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+    // Reset.  Should synthesize button up.
+    mapper->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateMotions) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addMapperAndConfigure(mapper);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT,
+            DISPLAY_ORIENTATION_90);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
+}
+
+TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldRotateMotions) {
+    CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+    addConfigurationProperty("cursor.mode", "navigation");
+    addConfigurationProperty("cursor.orientationAware", "1");
+    addMapperAndConfigure(mapper);
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_90);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1,  1));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_180);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1, -1));
+
+    mFakePolicy->setDisplayInfo(DISPLAY_ID,
+            DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_270);
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1, -1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1, -1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0, -1,  1,  0));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0, -1));
+    ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1, -1));
+}
+
+
+// --- TouchInputMapperTest ---
+
+class TouchInputMapperTest : public InputMapperTest {
+protected:
+    static const int32_t RAW_X_MIN;
+    static const int32_t RAW_X_MAX;
+    static const int32_t RAW_Y_MIN;
+    static const int32_t RAW_Y_MAX;
+    static const int32_t RAW_TOUCH_MIN;
+    static const int32_t RAW_TOUCH_MAX;
+    static const int32_t RAW_TOOL_MIN;
+    static const int32_t RAW_TOOL_MAX;
+    static const int32_t RAW_PRESSURE_MIN;
+    static const int32_t RAW_PRESSURE_MAX;
+    static const int32_t RAW_ORIENTATION_MIN;
+    static const int32_t RAW_ORIENTATION_MAX;
+    static const int32_t RAW_ID_MIN;
+    static const int32_t RAW_ID_MAX;
+    static const float X_PRECISION;
+    static const float Y_PRECISION;
+
+    static const VirtualKeyDefinition VIRTUAL_KEYS[2];
+
+    enum Axes {
+        POSITION = 1 << 0,
+        TOUCH = 1 << 1,
+        TOOL = 1 << 2,
+        PRESSURE = 1 << 3,
+        ORIENTATION = 1 << 4,
+        MINOR = 1 << 5,
+        ID = 1 << 6,
+    };
+
+    void prepareDisplay(int32_t orientation);
+    void prepareVirtualKeys();
+    int32_t toRawX(float displayX);
+    int32_t toRawY(float displayY);
+    float toDisplayX(int32_t rawX);
+    float toDisplayY(int32_t rawY);
+};
+
+const int32_t TouchInputMapperTest::RAW_X_MIN = 25;
+const int32_t TouchInputMapperTest::RAW_X_MAX = 1020;
+const int32_t TouchInputMapperTest::RAW_Y_MIN = 30;
+const int32_t TouchInputMapperTest::RAW_Y_MAX = 1010;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MAX = 31;
+const int32_t TouchInputMapperTest::RAW_TOOL_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOOL_MAX = 15;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MIN = RAW_TOUCH_MIN;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MAX = RAW_TOUCH_MAX;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MIN = -7;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MAX = 7;
+const int32_t TouchInputMapperTest::RAW_ID_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_ID_MAX = 9;
+const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH;
+const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT;
+
+const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = {
+        { KEY_HOME, 60, DISPLAY_HEIGHT + 15, 20, 20 },
+        { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 },
+};
+
+void TouchInputMapperTest::prepareDisplay(int32_t orientation) {
+    mFakePolicy->setDisplayInfo(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation);
+}
+
+void TouchInputMapperTest::prepareVirtualKeys() {
+    mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[0]);
+    mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[1]);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, AKEYCODE_MENU, POLICY_FLAG_WAKE);
+}
+
+int32_t TouchInputMapperTest::toRawX(float displayX) {
+    return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH + RAW_X_MIN);
+}
+
+int32_t TouchInputMapperTest::toRawY(float displayY) {
+    return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT + RAW_Y_MIN);
+}
+
+float TouchInputMapperTest::toDisplayX(int32_t rawX) {
+    return float(rawX - RAW_X_MIN) * DISPLAY_WIDTH / (RAW_X_MAX - RAW_X_MIN);
+}
+
+float TouchInputMapperTest::toDisplayY(int32_t rawY) {
+    return float(rawY - RAW_Y_MIN) * DISPLAY_HEIGHT / (RAW_Y_MAX - RAW_Y_MIN);
+}
+
+
+// --- SingleTouchInputMapperTest ---
+
+class SingleTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+    void prepareAxes(int axes);
+
+    void processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+    void processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+    void processUp(SingleTouchInputMapper* mappery);
+    void processPressure(SingleTouchInputMapper* mapper, int32_t pressure);
+    void processToolMajor(SingleTouchInputMapper* mapper, int32_t toolMajor);
+    void processSync(SingleTouchInputMapper* mapper);
+};
+
+void SingleTouchInputMapperTest::prepareAxes(int axes) {
+    if (axes & POSITION) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+    }
+    if (axes & PRESSURE) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_PRESSURE, RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+    }
+    if (axes & TOOL) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_TOOL_WIDTH, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+    }
+}
+
+void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+}
+
+void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+}
+
+void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 0, 0);
+}
+
+void SingleTouchInputMapperTest::processPressure(
+        SingleTouchInputMapper* mapper, int32_t pressure) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, 0, pressure, 0);
+}
+
+void SingleTouchInputMapperTest::processToolMajor(
+        SingleTouchInputMapper* mapper, int32_t toolMajor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, 0, toolMajor, 0);
+}
+
+void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+}
+
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.deviceType", "touchPad");
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    addMapperAndConfigure(mapper);
+
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    // Unknown key.
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+    // Virtual key is down.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+
+    // Virtual key is up.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_UP, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, GetScanCodeState) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    // Unknown key.
+    ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+    // Virtual key is down.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+
+    // Virtual key is up.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    ASSERT_EQ(AKEY_STATE_UP, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    const int32_t keys[2] = { AKEYCODE_HOME, AKEYCODE_A };
+    uint8_t flags[2] = { 0, 0 };
+    ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 2, keys, flags));
+    ASSERT_TRUE(flags[0]);
+    ASSERT_FALSE(flags[1]);
+}
+
+TEST_F(SingleTouchInputMapperTest, Reset_WhenVirtualKeysAreDown_SendsUp) {
+    // Note: Ideally we should send cancels but the implementation is more straightforward
+    // with up and this will only happen if a device is forcibly removed.
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since key is down, synthesize key up.
+    mapper->reset();
+
+    FakeInputDispatcher::NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    //ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(SingleTouchInputMapperTest, Reset_WhenNothingIsPressed_NothingMuchHappens) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Release virtual key.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+    // Reset.  Since no key is down, nothing happens.
+    mapper->reset();
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyKeyArgs args;
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Release virtual key.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+    ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+    ASSERT_EQ(KEY_HOME, args.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Should not have sent any motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyKeyArgs keyArgs;
+
+    // Press virtual key.
+    int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+    int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs));
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, keyArgs.flags);
+    ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+    ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+    // Move out of bounds.  This should generate a cancel and a pointer down since we moved
+    // into the display area.
+    y -= 100;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs));
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+    ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+            | AKEY_EVENT_FLAG_CANCELED, keyArgs.flags);
+    ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+    ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+    ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Keep moving out of bounds.  Should generate a pointer move.
+    y -= 50;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Release out of bounds.  Should generate a pointer up.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Initially go down out of bounds.
+    int32_t x = -10;
+    int32_t y = -10;
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+
+    // Move into the display area.  Should generate a pointer down.
+    x = 50;
+    y = 75;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Release.  Should generate a pointer up.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Down.
+    int32_t x = 100;
+    int32_t y = 125;
+    processDown(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Move.
+    x += 50;
+    y += 75;
+    processMove(mapper, x, y);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Up.
+    processUp(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_DoesNotRotateMotions) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.orientationAware", "0");
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Rotation 90.
+    prepareDisplay(DISPLAY_ORIENTATION_90);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(50, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(75, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_RotatesMotions) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareAxes(POSITION);
+    addMapperAndConfigure(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+
+    // Rotation 0.
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(50, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(75, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+    // Rotation 90.
+    prepareDisplay(DISPLAY_ORIENTATION_90);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(75, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+    // Rotation 180.
+    prepareDisplay(DISPLAY_ORIENTATION_180);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+    // Rotation 270.
+    prepareDisplay(DISPLAY_ORIENTATION_270);
+    processDown(mapper, toRawX(50), toRawY(75));
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].x, 1);
+    ASSERT_NEAR(50, args.pointerCoords[0].y, 1);
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
+    SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | PRESSURE | TOOL);
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawPressure = 10;
+    int32_t rawToolMajor = 12;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+    float size = float(rawToolMajor) / RAW_TOOL_MAX;
+    float tool = min(DISPLAY_WIDTH, DISPLAY_HEIGHT) * size;
+    float touch = min(tool * pressure, tool);
+
+    processDown(mapper, rawX, rawY);
+    processPressure(mapper, rawPressure);
+    processToolMajor(mapper, rawToolMajor);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touch, touch, tool, tool, 0));
+}
+
+
+// --- MultiTouchInputMapperTest ---
+
+class MultiTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+    void prepareAxes(int axes);
+
+    void processPosition(MultiTouchInputMapper* mapper, int32_t x, int32_t y);
+    void processTouchMajor(MultiTouchInputMapper* mapper, int32_t touchMajor);
+    void processTouchMinor(MultiTouchInputMapper* mapper, int32_t touchMinor);
+    void processToolMajor(MultiTouchInputMapper* mapper, int32_t toolMajor);
+    void processToolMinor(MultiTouchInputMapper* mapper, int32_t toolMinor);
+    void processOrientation(MultiTouchInputMapper* mapper, int32_t orientation);
+    void processPressure(MultiTouchInputMapper* mapper, int32_t pressure);
+    void processId(MultiTouchInputMapper* mapper, int32_t id);
+    void processMTSync(MultiTouchInputMapper* mapper);
+    void processSync(MultiTouchInputMapper* mapper);
+};
+
+void MultiTouchInputMapperTest::prepareAxes(int axes) {
+    if (axes & POSITION) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+    }
+    if (axes & TOUCH) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MAJOR, RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+        if (axes & MINOR) {
+            mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MINOR,
+                    RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+        }
+    }
+    if (axes & TOOL) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MAJOR, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+        if (axes & MINOR) {
+            mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MINOR,
+                    RAW_TOOL_MAX, RAW_TOOL_MAX, 0, 0);
+        }
+    }
+    if (axes & ORIENTATION) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_ORIENTATION,
+                RAW_ORIENTATION_MIN, RAW_ORIENTATION_MAX, 0, 0);
+    }
+    if (axes & PRESSURE) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_PRESSURE,
+                RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+    }
+    if (axes & ID) {
+        mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TRACKING_ID,
+                RAW_ID_MIN, RAW_ID_MAX, 0, 0);
+    }
+}
+
+void MultiTouchInputMapperTest::processPosition(
+        MultiTouchInputMapper* mapper, int32_t x, int32_t y) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, 0, x, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, 0, y, 0);
+}
+
+void MultiTouchInputMapperTest::processTouchMajor(
+        MultiTouchInputMapper* mapper, int32_t touchMajor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, 0, touchMajor, 0);
+}
+
+void MultiTouchInputMapperTest::processTouchMinor(
+        MultiTouchInputMapper* mapper, int32_t touchMinor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, 0, touchMinor, 0);
+}
+
+void MultiTouchInputMapperTest::processToolMajor(
+        MultiTouchInputMapper* mapper, int32_t toolMajor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, 0, toolMajor, 0);
+}
+
+void MultiTouchInputMapperTest::processToolMinor(
+        MultiTouchInputMapper* mapper, int32_t toolMinor) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, 0, toolMinor, 0);
+}
+
+void MultiTouchInputMapperTest::processOrientation(
+        MultiTouchInputMapper* mapper, int32_t orientation) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, 0, orientation, 0);
+}
+
+void MultiTouchInputMapperTest::processPressure(
+        MultiTouchInputMapper* mapper, int32_t pressure) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, 0, pressure, 0);
+}
+
+void MultiTouchInputMapperTest::processId(
+        MultiTouchInputMapper* mapper, int32_t id) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, 0, id, 0);
+}
+
+void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0, 0, 0);
+}
+
+void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) {
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+}
+
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Two fingers down at once.
+    int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+    processPosition(mapper, x1, y1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Move.
+    x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+    processPosition(mapper, x1, y1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // First finger up.
+    x2 += 15; y2 -= 20;
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Move.
+    x2 += 20; y2 -= 25;
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // New finger down.
+    int32_t x3 = 700, y3 = 300;
+    processPosition(mapper, x2, y2);
+    processMTSync(mapper);
+    processPosition(mapper, x3, y3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Second finger up.
+    x3 += 30; y3 -= 20;
+    processPosition(mapper, x3, y3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_EQ(1, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Last finger up.
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+    ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(0, motionArgs.flags);
+    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+    ASSERT_EQ(0, motionArgs.edgeFlags);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(0, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+    ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+    ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | ID);
+    prepareVirtualKeys();
+    addMapperAndConfigure(mapper);
+
+    mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+    FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+    // Two fingers down at once.
+    int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+    processPosition(mapper, x1, y1);
+    processId(mapper, 1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_EQ(2, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // Move.
+    x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+    processPosition(mapper, x1, y1);
+    processId(mapper, 1);
+    processMTSync(mapper);
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_EQ(2, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // First finger up.
+    x2 += 15; y2 -= 20;
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(1, motionArgs.pointerIds[0]);
+    ASSERT_EQ(2, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // Move.
+    x2 += 20; y2 -= 25;
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+    // New finger down.
+    int32_t x3 = 700, y3 = 300;
+    processPosition(mapper, x2, y2);
+    processId(mapper, 2);
+    processMTSync(mapper);
+    processPosition(mapper, x3, y3);
+    processId(mapper, 3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_EQ(3, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    // Second finger up.
+    x3 += 30; y3 -= 20;
+    processPosition(mapper, x3, y3);
+    processId(mapper, 3);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            motionArgs.action);
+    ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+    ASSERT_EQ(2, motionArgs.pointerIds[0]);
+    ASSERT_EQ(3, motionArgs.pointerIds[1]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(3, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    // Last finger up.
+    processMTSync(mapper);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+    ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+    ASSERT_EQ(3, motionArgs.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+            toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+    // Should not have sent any more keys or motions.
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR);
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawTouchMajor = 7;
+    int32_t rawTouchMinor = 6;
+    int32_t rawToolMajor = 9;
+    int32_t rawToolMinor = 8;
+    int32_t rawPressure = 11;
+    int32_t rawOrientation = 3;
+    int32_t id = 5;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+    float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX;
+    float toolMajor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMajor / RAW_TOOL_MAX;
+    float toolMinor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMinor / RAW_TOOL_MAX;
+    float touchMajor = min(toolMajor * pressure, toolMajor);
+    float touchMinor = min(toolMinor * pressure, toolMinor);
+    float orientation = float(rawOrientation) / RAW_ORIENTATION_MAX * M_PI_2;
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processTouchMinor(mapper, rawTouchMinor);
+    processToolMajor(mapper, rawToolMajor);
+    processToolMinor(mapper, rawToolMinor);
+    processPressure(mapper, rawPressure);
+    processOrientation(mapper, rawOrientation);
+    processId(mapper, id);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(id, args.pointerIds[0]);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, orientation));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL | MINOR);
+    addConfigurationProperty("touch.touchSize.calibration", "geometric");
+    addConfigurationProperty("touch.toolSize.calibration", "geometric");
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawTouchMajor = 140;
+    int32_t rawTouchMinor = 120;
+    int32_t rawToolMajor = 180;
+    int32_t rawToolMinor = 160;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawTouchMajor) / RAW_TOUCH_MAX;
+    float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX;
+    float scale = avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN),
+            float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN));
+    float toolMajor = float(rawToolMajor) * scale;
+    float toolMinor = float(rawToolMinor) * scale;
+    float touchMajor = min(float(rawTouchMajor) * scale, toolMajor);
+    float touchMinor = min(float(rawTouchMinor) * scale, toolMinor);
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processTouchMinor(mapper, rawTouchMinor);
+    processToolMajor(mapper, rawToolMajor);
+    processToolMinor(mapper, rawToolMinor);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_SummedLinearCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL);
+    addConfigurationProperty("touch.touchSize.calibration", "pressure");
+    addConfigurationProperty("touch.toolSize.calibration", "linear");
+    addConfigurationProperty("touch.toolSize.linearScale", "10");
+    addConfigurationProperty("touch.toolSize.linearBias", "160");
+    addConfigurationProperty("touch.toolSize.isSummed", "1");
+    addConfigurationProperty("touch.pressure.calibration", "amplitude");
+    addConfigurationProperty("touch.pressure.source", "touch");
+    addConfigurationProperty("touch.pressure.scale", "0.01");
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    // Note: We only provide a single common touch/tool value because the device is assumed
+    //       not to emit separate values for each pointer (isSummed = 1).
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawX2 = 150;
+    int32_t rawY2 = 250;
+    int32_t rawTouchMajor = 60;
+    int32_t rawToolMajor = 5;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float x2 = toDisplayX(rawX2);
+    float y2 = toDisplayY(rawY2);
+    float pressure = float(rawTouchMajor) * 0.01f;
+    float size = float(rawToolMajor) / RAW_TOOL_MAX;
+    float tool = (float(rawToolMajor) * 10.0f + 160.0f) / 2;
+    float touch = min(tool * pressure, tool);
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processToolMajor(mapper, rawToolMajor);
+    processMTSync(mapper);
+    processPosition(mapper, rawX2, rawY2);
+    processTouchMajor(mapper, rawTouchMajor);
+    processToolMajor(mapper, rawToolMajor);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+            args.action);
+    ASSERT_EQ(size_t(2), args.pointerCount);
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touch, touch, tool, tool, 0));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[1],
+            x2, y2, pressure, size, touch, touch, tool, tool, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_AreaCalibration) {
+    MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | TOUCH | TOOL);
+    addConfigurationProperty("touch.touchSize.calibration", "pressure");
+    addConfigurationProperty("touch.toolSize.calibration", "area");
+    addConfigurationProperty("touch.toolSize.areaScale", "22");
+    addConfigurationProperty("touch.toolSize.areaBias", "1");
+    addConfigurationProperty("touch.toolSize.linearScale", "9.2");
+    addConfigurationProperty("touch.toolSize.linearBias", "3");
+    addConfigurationProperty("touch.pressure.calibration", "amplitude");
+    addConfigurationProperty("touch.pressure.source", "touch");
+    addConfigurationProperty("touch.pressure.scale", "0.01");
+    addMapperAndConfigure(mapper);
+
+    // These calculations are based on the input device calibration documentation.
+    int32_t rawX = 100;
+    int32_t rawY = 200;
+    int32_t rawTouchMajor = 60;
+    int32_t rawToolMajor = 5;
+
+    float x = toDisplayX(rawX);
+    float y = toDisplayY(rawY);
+    float pressure = float(rawTouchMajor) * 0.01f;
+    float size = float(rawToolMajor) / RAW_TOOL_MAX;
+    float tool = sqrtf(float(rawToolMajor) * 22.0f + 1.0f) * 9.2f + 3.0f;
+    float touch = min(tool * pressure, tool);
+
+    processPosition(mapper, rawX, rawY);
+    processTouchMajor(mapper, rawTouchMajor);
+    processToolMajor(mapper, rawToolMajor);
+    processMTSync(mapper);
+    processSync(mapper);
+
+    FakeInputDispatcher::NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+            x, y, pressure, size, touch, touch, tool, tool, 0));
+}
+
+} // namespace android