diff --git a/include/powermanager/PowerManager.h b/include/powermanager/PowerManager.h index 45901747e..cbddc1153 100644 --- a/include/powermanager/PowerManager.h +++ b/include/powermanager/PowerManager.h @@ -24,6 +24,14 @@ enum { POWERMANAGER_PARTIAL_WAKE_LOCK = 1, // equals PowerManager.PARTIAL_WAKE_LOCK constant }; +enum { + USER_ACTIVITY_EVENT_OTHER = 0, + USER_ACTIVITY_EVENT_BUTTON = 1, + USER_ACTIVITY_EVENT_TOUCH = 2, + + USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_TOUCH, // Last valid event code. +}; + }; // namespace android #endif // ANDROID_POWERMANAGER_H diff --git a/services/inputflinger/Android.mk b/services/inputflinger/Android.mk index e32d38a91..574c14e36 100644 --- a/services/inputflinger/Android.mk +++ b/services/inputflinger/Android.mk @@ -16,6 +16,13 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + EventHub.cpp \ + InputApplication.cpp \ + InputDispatcher.cpp \ + InputListener.cpp \ + InputManager.cpp \ + InputReader.cpp \ + InputWindow.cpp \ InputFlinger.cpp LOCAL_SHARED_LIBRARIES := \ @@ -23,9 +30,15 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libinput \ liblog \ - libutils + libutils \ + libui \ + libhardware_legacy -LOCAL_CFLAGS += -fvisibility=hidden + +# TODO: Move inputflinger to its own process and mark it hidden +#LOCAL_CFLAGS += -fvisibility=hidden + +LOCAL_CFLAGS += -Wno-unused-parameter LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) diff --git a/services/inputflinger/EventHub.cpp b/services/inputflinger/EventHub.cpp new file mode 100644 index 000000000..e2efd17f5 --- /dev/null +++ b/services/inputflinger/EventHub.cpp @@ -0,0 +1,1669 @@ +/* + * 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. + */ + +#define LOG_TAG "EventHub" + +// #define LOG_NDEBUG 0 + +#include "EventHub.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* this macro is used to tell if "bit" is set in "array" + * it selects a byte from the array, and does a boolean AND + * operation with a byte that only has the relevant bit set. + * eg. to check for the 12th bit, we do (array[1] & 1<<4) + */ +#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) + +/* this macro computes the number of bytes needed to represent a bit array of the specified size */ +#define sizeof_bit_array(bits) ((bits + 7) / 8) + +#define INDENT " " +#define INDENT2 " " +#define INDENT3 " " + +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"; +} + +static String8 sha1(const String8& in) { + SHA1_CTX ctx; + SHA1Init(&ctx); + SHA1Update(&ctx, reinterpret_cast(in.string()), in.size()); + u_char digest[SHA1_DIGEST_LENGTH]; + SHA1Final(digest, &ctx); + + String8 out; + for (size_t i = 0; i < SHA1_DIGEST_LENGTH; i++) { + out.appendFormat("%02x", digest[i]); + } + return out; +} + +static void getLinuxRelease(int* major, int* minor) { + struct utsname info; + if (uname(&info) || sscanf(info.release, "%d.%d", major, minor) <= 0) { + *major = 0, *minor = 0; + ALOGE("Could not get linux version: %s", strerror(errno)); + } +} + +// --- Global Functions --- + +uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) { + // Touch devices get dibs on touch-related axes. + if (deviceClasses & INPUT_DEVICE_CLASS_TOUCH) { + switch (axis) { + case ABS_X: + case ABS_Y: + case ABS_PRESSURE: + case ABS_TOOL_WIDTH: + case ABS_DISTANCE: + case ABS_TILT_X: + case ABS_TILT_Y: + case ABS_MT_SLOT: + case ABS_MT_TOUCH_MAJOR: + case ABS_MT_TOUCH_MINOR: + case ABS_MT_WIDTH_MAJOR: + case ABS_MT_WIDTH_MINOR: + case ABS_MT_ORIENTATION: + case ABS_MT_POSITION_X: + case ABS_MT_POSITION_Y: + case ABS_MT_TOOL_TYPE: + case ABS_MT_BLOB_ID: + case ABS_MT_TRACKING_ID: + case ABS_MT_PRESSURE: + case ABS_MT_DISTANCE: + return INPUT_DEVICE_CLASS_TOUCH; + } + } + + // Joystick devices get the rest. + return deviceClasses & INPUT_DEVICE_CLASS_JOYSTICK; +} + +// --- 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), configuration(NULL), virtualKeyMap(NULL), + ffEffectPlaying(false), ffEffectId(-1), controllerNumber(0), + timestampOverrideSec(0), timestampOverrideUsec(0) { + memset(keyBitmask, 0, sizeof(keyBitmask)); + memset(absBitmask, 0, sizeof(absBitmask)); + memset(relBitmask, 0, sizeof(relBitmask)); + memset(swBitmask, 0, sizeof(swBitmask)); + memset(ledBitmask, 0, sizeof(ledBitmask)); + memset(ffBitmask, 0, sizeof(ffBitmask)); + memset(propBitmask, 0, sizeof(propBitmask)); +} + +EventHub::Device::~Device() { + close(); + delete configuration; + delete virtualKeyMap; +} + +void EventHub::Device::close() { + if (fd >= 0) { + ::close(fd); + fd = -1; + } +} + + +// --- EventHub --- + +const uint32_t EventHub::EPOLL_ID_INOTIFY; +const uint32_t EventHub::EPOLL_ID_WAKE; +const int EventHub::EPOLL_SIZE_HINT; +const int EventHub::EPOLL_MAX_EVENTS; + +EventHub::EventHub(void) : + mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(), + mOpeningDevices(0), mClosingDevices(0), + mNeedToSendFinishedDeviceScan(false), + mNeedToReopenDevices(false), mNeedToScanDevices(true), + mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) { + acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); + + mEpollFd = epoll_create(EPOLL_SIZE_HINT); + LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno); + + mINotifyFd = inotify_init(); + int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE); + LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d", + DEVICE_PATH, errno); + + struct epoll_event eventItem; + memset(&eventItem, 0, sizeof(eventItem)); + eventItem.events = EPOLLIN; + eventItem.data.u32 = EPOLL_ID_INOTIFY; + result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno); + + int wakeFds[2]; + result = pipe(wakeFds); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); + + mWakeReadPipeFd = wakeFds[0]; + mWakeWritePipeFd = wakeFds[1]; + + result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d", + errno); + + result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d", + errno); + + eventItem.data.u32 = EPOLL_ID_WAKE; + result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d", + errno); + + int major, minor; + getLinuxRelease(&major, &minor); + // EPOLLWAKEUP was introduced in kernel 3.5 + mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5); +} + +EventHub::~EventHub(void) { + closeAllDevicesLocked(); + + while (mClosingDevices) { + Device* device = mClosingDevices; + mClosingDevices = device->next; + delete device; + } + + ::close(mEpollFd); + ::close(mINotifyFd); + ::close(mWakeReadPipeFd); + ::close(mWakeWritePipeFd); + + release_wake_lock(WAKE_LOCK_ID); +} + +InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == NULL) return InputDeviceIdentifier(); + return device->identifier; +} + +uint32_t EventHub::getDeviceClasses(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == NULL) return 0; + return device->classes; +} + +int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == NULL) return 0; + return device->controllerNumber; +} + +void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && device->configuration) { + *outConfiguration = *device->configuration; + } else { + outConfiguration->clear(); + } +} + +status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const { + outAxisInfo->clear(); + + if (axis >= 0 && axis <= ABS_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) { + struct input_absinfo info; + if(ioctl(device->fd, EVIOCGABS(axis), &info)) { + ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", + axis, device->identifier.name.string(), device->fd, errno); + return -errno; + } + + if (info.minimum != info.maximum) { + outAxisInfo->valid = true; + outAxisInfo->minValue = info.minimum; + outAxisInfo->maxValue = info.maximum; + outAxisInfo->flat = info.flat; + outAxisInfo->fuzz = info.fuzz; + outAxisInfo->resolution = info.resolution; + } + return OK; + } + } + return -1; +} + +bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const { + if (axis >= 0 && axis <= REL_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device) { + return test_bit(axis, device->relBitmask); + } + } + return false; +} + +bool EventHub::hasInputProperty(int32_t deviceId, int property) const { + if (property >= 0 && property <= INPUT_PROP_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device) { + return test_bit(property, device->propBitmask); + } + } + return false; +} + +int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const { + if (scanCode >= 0 && scanCode <= KEY_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device && !device->isVirtual() && test_bit(scanCode, device->keyBitmask)) { + uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)]; + memset(keyState, 0, sizeof(keyState)); + if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) { + return test_bit(scanCode, keyState) ? AKEY_STATE_DOWN : AKEY_STATE_UP; + } + } + } + return AKEY_STATE_UNKNOWN; +} + +int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device && !device->isVirtual() && device->keyMap.haveKeyLayout()) { + Vector scanCodes; + device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode, &scanCodes); + if (scanCodes.size() != 0) { + uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)]; + memset(keyState, 0, sizeof(keyState)); + if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) { + for (size_t i = 0; i < scanCodes.size(); i++) { + int32_t sc = scanCodes.itemAt(i); + if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, keyState)) { + return AKEY_STATE_DOWN; + } + } + return AKEY_STATE_UP; + } + } + } + return AKEY_STATE_UNKNOWN; +} + +int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const { + if (sw >= 0 && sw <= SW_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device && !device->isVirtual() && test_bit(sw, device->swBitmask)) { + uint8_t swState[sizeof_bit_array(SW_MAX + 1)]; + memset(swState, 0, sizeof(swState)); + if (ioctl(device->fd, EVIOCGSW(sizeof(swState)), swState) >= 0) { + return test_bit(sw, swState) ? AKEY_STATE_DOWN : AKEY_STATE_UP; + } + } + } + return AKEY_STATE_UNKNOWN; +} + +status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const { + *outValue = 0; + + if (axis >= 0 && axis <= ABS_MAX) { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) { + struct input_absinfo info; + if(ioctl(device->fd, EVIOCGABS(axis), &info)) { + ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", + axis, device->identifier.name.string(), device->fd, errno); + return -errno; + } + + *outValue = info.value; + return OK; + } + } + return -1; +} + +bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const { + AutoMutex _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device && device->keyMap.haveKeyLayout()) { + Vector scanCodes; + for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { + scanCodes.clear(); + + status_t err = device->keyMap.keyLayoutMap->findScanCodesForKey( + keyCodes[codeIndex], &scanCodes); + if (! err) { + // check the possible scan codes identified by the layout map against the + // map of codes actually emitted by the driver + for (size_t sc = 0; sc < scanCodes.size(); sc++) { + if (test_bit(scanCodes[sc], device->keyBitmask)) { + outFlags[codeIndex] = 1; + break; + } + } + } + } + return true; + } + return false; +} + +status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, + int32_t* outKeycode, uint32_t* outFlags) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + + if (device) { + // Check the key character map first. + sp kcm = device->getKeyCharacterMap(); + if (kcm != NULL) { + if (!kcm->mapKey(scanCode, usageCode, outKeycode)) { + *outFlags = 0; + return NO_ERROR; + } + } + + // Check the key layout next. + if (device->keyMap.haveKeyLayout()) { + if (!device->keyMap.keyLayoutMap->mapKey( + scanCode, usageCode, outKeycode, outFlags)) { + return NO_ERROR; + } + } + } + + *outKeycode = 0; + *outFlags = 0; + return NAME_NOT_FOUND; +} + +status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + + if (device && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->mapAxis(scanCode, outAxisInfo); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + + return NAME_NOT_FOUND; +} + +void EventHub::setExcludedDevices(const Vector& devices) { + AutoMutex _l(mLock); + + mExcludedDevices = devices; +} + +bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && scanCode >= 0 && scanCode <= KEY_MAX) { + if (test_bit(scanCode, device->keyBitmask)) { + return true; + } + } + return false; +} + +bool EventHub::hasLed(int32_t deviceId, int32_t led) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + int32_t sc; + if (device && mapLed(device, led, &sc) == NO_ERROR) { + if (test_bit(sc, device->ledBitmask)) { + return true; + } + } + return false; +} + +void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + setLedStateLocked(device, led, on); +} + +void EventHub::setLedStateLocked(Device* device, int32_t led, bool on) { + int32_t sc; + if (device && !device->isVirtual() && mapLed(device, led, &sc) != NAME_NOT_FOUND) { + struct input_event ev; + ev.time.tv_sec = 0; + ev.time.tv_usec = 0; + ev.type = EV_LED; + ev.code = sc; + ev.value = on ? 1 : 0; + + ssize_t nWrite; + do { + nWrite = write(device->fd, &ev, sizeof(struct input_event)); + } while (nWrite == -1 && errno == EINTR); + } +} + +void EventHub::getVirtualKeyDefinitions(int32_t deviceId, + Vector& outVirtualKeys) const { + outVirtualKeys.clear(); + + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && device->virtualKeyMap) { + outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys()); + } +} + +sp EventHub::getKeyCharacterMap(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device) { + return device->getKeyCharacterMap(); + } + return NULL; +} + +bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, + const sp& map) { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device) { + if (map != device->overlayKeyMap) { + device->overlayKeyMap = map; + device->combinedKeyMap = KeyCharacterMap::combine( + device->keyMap.keyCharacterMap, map); + return true; + } + } + return false; +} + +static String8 generateDescriptor(InputDeviceIdentifier& identifier) { + String8 rawDescriptor; + rawDescriptor.appendFormat(":%04x:%04x:", identifier.vendor, + identifier.product); + // TODO add handling for USB devices to not uniqueify kbs that show up twice + if (!identifier.uniqueId.isEmpty()) { + rawDescriptor.append("uniqueId:"); + rawDescriptor.append(identifier.uniqueId); + } else if (identifier.nonce != 0) { + rawDescriptor.appendFormat("nonce:%04x", identifier.nonce); + } + + if (identifier.vendor == 0 && identifier.product == 0) { + // If we don't know the vendor and product id, then the device is probably + // built-in so we need to rely on other information to uniquely identify + // the input device. Usually we try to avoid relying on the device name or + // location but for built-in input device, they are unlikely to ever change. + if (!identifier.name.isEmpty()) { + rawDescriptor.append("name:"); + rawDescriptor.append(identifier.name); + } else if (!identifier.location.isEmpty()) { + rawDescriptor.append("location:"); + rawDescriptor.append(identifier.location); + } + } + identifier.descriptor = sha1(rawDescriptor); + return rawDescriptor; +} + +void EventHub::assignDescriptorLocked(InputDeviceIdentifier& identifier) { + // Compute a device descriptor that uniquely identifies the device. + // The descriptor is assumed to be a stable identifier. Its value should not + // change between reboots, reconnections, firmware updates or new releases + // of Android. In practice we sometimes get devices that cannot be uniquely + // identified. In this case we enforce uniqueness between connected devices. + // Ideally, we also want the descriptor to be short and relatively opaque. + + identifier.nonce = 0; + String8 rawDescriptor = generateDescriptor(identifier); + if (identifier.uniqueId.isEmpty()) { + // If it didn't have a unique id check for conflicts and enforce + // uniqueness if necessary. + while(getDeviceByDescriptorLocked(identifier.descriptor) != NULL) { + identifier.nonce++; + rawDescriptor = generateDescriptor(identifier); + } + } + ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.string(), + identifier.descriptor.string()); +} + +void EventHub::vibrate(int32_t deviceId, nsecs_t duration) { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && !device->isVirtual()) { + ff_effect effect; + memset(&effect, 0, sizeof(effect)); + effect.type = FF_RUMBLE; + effect.id = device->ffEffectId; + effect.u.rumble.strong_magnitude = 0xc000; + effect.u.rumble.weak_magnitude = 0xc000; + effect.replay.length = (duration + 999999LL) / 1000000LL; + effect.replay.delay = 0; + if (ioctl(device->fd, EVIOCSFF, &effect)) { + ALOGW("Could not upload force feedback effect to device %s due to error %d.", + device->identifier.name.string(), errno); + return; + } + device->ffEffectId = effect.id; + + struct input_event ev; + ev.time.tv_sec = 0; + ev.time.tv_usec = 0; + ev.type = EV_FF; + ev.code = device->ffEffectId; + ev.value = 1; + if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) { + ALOGW("Could not start force feedback effect on device %s due to error %d.", + device->identifier.name.string(), errno); + return; + } + device->ffEffectPlaying = true; + } +} + +void EventHub::cancelVibrate(int32_t deviceId) { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && !device->isVirtual()) { + if (device->ffEffectPlaying) { + device->ffEffectPlaying = false; + + struct input_event ev; + ev.time.tv_sec = 0; + ev.time.tv_usec = 0; + ev.type = EV_FF; + ev.code = device->ffEffectId; + ev.value = 0; + if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) { + ALOGW("Could not stop force feedback effect on device %s due to error %d.", + device->identifier.name.string(), errno); + return; + } + } + } +} + +EventHub::Device* EventHub::getDeviceByDescriptorLocked(String8& descriptor) const { + size_t size = mDevices.size(); + for (size_t i = 0; i < size; i++) { + Device* device = mDevices.valueAt(i); + if (descriptor.compare(device->identifier.descriptor) == 0) { + return device; + } + } + return NULL; +} + +EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const { + if (deviceId == BUILT_IN_KEYBOARD_ID) { + deviceId = mBuiltInKeyboardId; + } + ssize_t index = mDevices.indexOfKey(deviceId); + return index >= 0 ? mDevices.valueAt(index) : NULL; +} + +EventHub::Device* EventHub::getDeviceByPathLocked(const char* devicePath) const { + for (size_t i = 0; i < mDevices.size(); i++) { + Device* device = mDevices.valueAt(i); + if (device->path == devicePath) { + return device; + } + } + return NULL; +} + +size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { + ALOG_ASSERT(bufferSize >= 1); + + AutoMutex _l(mLock); + + struct input_event readBuffer[bufferSize]; + + RawEvent* event = buffer; + size_t capacity = bufferSize; + bool awoken = false; + for (;;) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + + // Reopen input devices if needed. + if (mNeedToReopenDevices) { + mNeedToReopenDevices = false; + + ALOGI("Reopening all input devices due to a configuration change."); + + closeAllDevicesLocked(); + mNeedToScanDevices = true; + break; // return to the caller before we actually rescan + } + + // Report any devices that had last been added/removed. + while (mClosingDevices) { + Device* device = mClosingDevices; + ALOGV("Reporting device closed: id=%d, name=%s\n", + device->id, device->path.string()); + mClosingDevices = device->next; + event->when = now; + event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id; + event->type = DEVICE_REMOVED; + event += 1; + delete device; + mNeedToSendFinishedDeviceScan = true; + if (--capacity == 0) { + break; + } + } + + if (mNeedToScanDevices) { + mNeedToScanDevices = false; + scanDevicesLocked(); + mNeedToSendFinishedDeviceScan = true; + } + + while (mOpeningDevices != NULL) { + Device* device = mOpeningDevices; + ALOGV("Reporting device opened: id=%d, name=%s\n", + device->id, device->path.string()); + mOpeningDevices = device->next; + event->when = now; + event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + event->type = DEVICE_ADDED; + event += 1; + mNeedToSendFinishedDeviceScan = true; + if (--capacity == 0) { + break; + } + } + + if (mNeedToSendFinishedDeviceScan) { + mNeedToSendFinishedDeviceScan = false; + event->when = now; + event->type = FINISHED_DEVICE_SCAN; + event += 1; + if (--capacity == 0) { + break; + } + } + + // Grab the next input event. + bool deviceChanged = false; + while (mPendingEventIndex < mPendingEventCount) { + const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++]; + if (eventItem.data.u32 == EPOLL_ID_INOTIFY) { + if (eventItem.events & EPOLLIN) { + mPendingINotify = true; + } else { + ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events); + } + continue; + } + + if (eventItem.data.u32 == EPOLL_ID_WAKE) { + if (eventItem.events & EPOLLIN) { + ALOGV("awoken after wake()"); + awoken = true; + char buffer[16]; + ssize_t nRead; + do { + nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); + } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer)); + } else { + ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.", + eventItem.events); + } + continue; + } + + ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32); + if (deviceIndex < 0) { + ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.", + eventItem.events, eventItem.data.u32); + continue; + } + + Device* device = mDevices.valueAt(deviceIndex); + if (eventItem.events & EPOLLIN) { + int32_t readSize = read(device->fd, readBuffer, + sizeof(struct input_event) * capacity); + if (readSize == 0 || (readSize < 0 && errno == ENODEV)) { + // Device was removed before INotify noticed. + ALOGW("could not get event, removed? (fd: %d size: %d bufferSize: %d " + "capacity: %d errno: %d)\n", + device->fd, readSize, bufferSize, capacity, errno); + deviceChanged = true; + closeDeviceLocked(device); + } else if (readSize < 0) { + if (errno != EAGAIN && errno != EINTR) { + ALOGW("could not get event (errno=%d)", errno); + } + } else if ((readSize % sizeof(struct input_event)) != 0) { + ALOGE("could not get event (wrong size: %d)", readSize); + } else { + int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + + size_t count = size_t(readSize) / sizeof(struct input_event); + for (size_t i = 0; i < count; i++) { + struct input_event& iev = readBuffer[i]; + ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d", + device->path.string(), + (int) iev.time.tv_sec, (int) iev.time.tv_usec, + iev.type, iev.code, iev.value); + + // Some input devices may have a better concept of the time + // when an input event was actually generated than the kernel + // which simply timestamps all events on entry to evdev. + // This is a custom Android extension of the input protocol + // mainly intended for use with uinput based device drivers. + if (iev.type == EV_MSC) { + if (iev.code == MSC_ANDROID_TIME_SEC) { + device->timestampOverrideSec = iev.value; + continue; + } else if (iev.code == MSC_ANDROID_TIME_USEC) { + device->timestampOverrideUsec = iev.value; + continue; + } + } + if (device->timestampOverrideSec || device->timestampOverrideUsec) { + iev.time.tv_sec = device->timestampOverrideSec; + iev.time.tv_usec = device->timestampOverrideUsec; + if (iev.type == EV_SYN && iev.code == SYN_REPORT) { + device->timestampOverrideSec = 0; + device->timestampOverrideUsec = 0; + } + ALOGV("applied override time %d.%06d", + int(iev.time.tv_sec), int(iev.time.tv_usec)); + } + +#ifdef HAVE_POSIX_CLOCKS + // Use the time specified in the event instead of the current time + // so that downstream code can get more accurate estimates of + // event dispatch latency from the time the event is enqueued onto + // the evdev client buffer. + // + // The event's timestamp fortuitously uses the same monotonic clock + // time base as the rest of Android. The kernel event device driver + // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts(). + // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere + // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a + // system call that also queries ktime_get_ts(). + event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL + + nsecs_t(iev.time.tv_usec) * 1000LL; + ALOGV("event time %lld, now %lld", event->when, now); + + // Bug 7291243: Add a guard in case the kernel generates timestamps + // that appear to be far into the future because they were generated + // using the wrong clock source. + // + // This can happen because when the input device is initially opened + // it has a default clock source of CLOCK_REALTIME. Any input events + // enqueued right after the device is opened will have timestamps + // generated using CLOCK_REALTIME. We later set the clock source + // to CLOCK_MONOTONIC but it is already too late. + // + // Invalid input event timestamps can result in ANRs, crashes and + // and other issues that are hard to track down. We must not let them + // propagate through the system. + // + // Log a warning so that we notice the problem and recover gracefully. + if (event->when >= now + 10 * 1000000000LL) { + // Double-check. Time may have moved on. + nsecs_t time = systemTime(SYSTEM_TIME_MONOTONIC); + if (event->when > time) { + ALOGW("An input event from %s has a timestamp that appears to " + "have been generated using the wrong clock source " + "(expected CLOCK_MONOTONIC): " + "event time %lld, current time %lld, call time %lld. " + "Using current time instead.", + device->path.string(), event->when, time, now); + event->when = time; + } else { + ALOGV("Event time is ok but failed the fast path and required " + "an extra call to systemTime: " + "event time %lld, current time %lld, call time %lld.", + event->when, time, now); + } + } +#else + event->when = now; +#endif + event->deviceId = deviceId; + event->type = iev.type; + event->code = iev.code; + event->value = iev.value; + event += 1; + capacity -= 1; + } + if (capacity == 0) { + // The result buffer is full. Reset the pending event index + // so we will try to read the device again on the next iteration. + mPendingEventIndex -= 1; + break; + } + } + } else if (eventItem.events & EPOLLHUP) { + ALOGI("Removing device %s due to epoll hang-up event.", + device->identifier.name.string()); + deviceChanged = true; + closeDeviceLocked(device); + } else { + ALOGW("Received unexpected epoll event 0x%08x for device %s.", + eventItem.events, device->identifier.name.string()); + } + } + + // readNotify() will modify the list of devices so this must be done after + // processing all other events to ensure that we read all remaining events + // before closing the devices. + if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) { + mPendingINotify = false; + readNotifyLocked(); + deviceChanged = true; + } + + // Report added or removed devices immediately. + if (deviceChanged) { + continue; + } + + // Return now if we have collected any events or if we were explicitly awoken. + if (event != buffer || awoken) { + break; + } + + // Poll for events. Mind the wake lock dance! + // We hold a wake lock at all times except during epoll_wait(). This works due to some + // subtle choreography. When a device driver has pending (unread) events, it acquires + // a kernel wake lock. However, once the last pending event has been read, the device + // driver will release the kernel wake lock. To prevent the system from going to sleep + // when this happens, the EventHub holds onto its own user wake lock while the client + // is processing events. Thus the system can only sleep if there are no events + // pending or currently being processed. + // + // The timeout is advisory only. If the device is asleep, it will not wake just to + // service the timeout. + mPendingEventIndex = 0; + + mLock.unlock(); // release lock before poll, must be before release_wake_lock + release_wake_lock(WAKE_LOCK_ID); + + int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis); + + acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); + mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock + + if (pollResult == 0) { + // Timed out. + mPendingEventCount = 0; + break; + } + + if (pollResult < 0) { + // An error occurred. + mPendingEventCount = 0; + + // Sleep after errors to avoid locking up the system. + // Hopefully the error is transient. + if (errno != EINTR) { + ALOGW("poll failed (errno=%d)\n", errno); + usleep(100000); + } + } else { + // Some events occurred. + mPendingEventCount = size_t(pollResult); + } + } + + // All done, return the number of events we read. + return event - buffer; +} + +void EventHub::wake() { + ALOGV("wake() called"); + + ssize_t nWrite; + do { + nWrite = write(mWakeWritePipeFd, "W", 1); + } while (nWrite == -1 && errno == EINTR); + + if (nWrite != 1 && errno != EAGAIN) { + ALOGW("Could not write wake signal, errno=%d", errno); + } +} + +void EventHub::scanDevicesLocked() { + status_t res = scanDirLocked(DEVICE_PATH); + if(res < 0) { + ALOGE("scan dir failed for %s\n", DEVICE_PATH); + } + if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) { + createVirtualKeyboardLocked(); + } +} + +// ---------------------------------------------------------------------------- + +static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) { + const uint8_t* end = array + endIndex; + array += startIndex; + while (array != end) { + if (*(array++) != 0) { + return true; + } + } + return false; +} + +static const int32_t GAMEPAD_KEYCODES[] = { + AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C, + AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z, + AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1, + AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2, + AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR, + AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE, + AKEYCODE_BUTTON_1, AKEYCODE_BUTTON_2, AKEYCODE_BUTTON_3, AKEYCODE_BUTTON_4, + AKEYCODE_BUTTON_5, AKEYCODE_BUTTON_6, AKEYCODE_BUTTON_7, AKEYCODE_BUTTON_8, + AKEYCODE_BUTTON_9, AKEYCODE_BUTTON_10, AKEYCODE_BUTTON_11, AKEYCODE_BUTTON_12, + AKEYCODE_BUTTON_13, AKEYCODE_BUTTON_14, AKEYCODE_BUTTON_15, AKEYCODE_BUTTON_16, +}; + +status_t EventHub::openDeviceLocked(const char *devicePath) { + char buffer[80]; + + ALOGV("Opening device: %s", devicePath); + + int fd = open(devicePath, O_RDWR | O_CLOEXEC); + if(fd < 0) { + ALOGE("could not open %s, %s\n", devicePath, strerror(errno)); + return -1; + } + + InputDeviceIdentifier identifier; + + // Get device name. + if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) { + //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 + for (size_t i = 0; i < mExcludedDevices.size(); i++) { + const String8& item = mExcludedDevices.itemAt(i); + if (identifier.name == item) { + ALOGI("ignoring event id %s driver %s\n", devicePath, item.string()); + close(fd); + return -1; + } + } + + // Get device driver version. + int driverVersion; + if(ioctl(fd, EVIOCGVERSION, &driverVersion)) { + ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno)); + close(fd); + return -1; + } + + // Get device identifier. + struct input_id inputId; + if(ioctl(fd, EVIOCGID, &inputId)) { + ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno)); + close(fd); + return -1; + } + identifier.bus = inputId.bustype; + identifier.product = inputId.product; + identifier.vendor = inputId.vendor; + identifier.version = inputId.version; + + // Get device physical location. + if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) { + //fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno)); + } else { + buffer[sizeof(buffer) - 1] = '\0'; + identifier.location.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); + } + + // Fill in the descriptor. + assignDescriptorLocked(identifier); + + // Make file descriptor non-blocking for use with poll(). + if (fcntl(fd, F_SETFL, O_NONBLOCK)) { + ALOGE("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); + + ALOGV("add device %d: %s\n", deviceId, devicePath); + ALOGV(" bus: %04x\n" + " vendor %04x\n" + " product %04x\n" + " version %04x\n", + identifier.bus, identifier.vendor, identifier.product, identifier.version); + ALOGV(" name: \"%s\"\n", identifier.name.string()); + ALOGV(" location: \"%s\"\n", identifier.location.string()); + ALOGV(" unique id: \"%s\"\n", identifier.uniqueId.string()); + ALOGV(" descriptor: \"%s\"\n", identifier.descriptor.string()); + ALOGV(" driver: v%d.%d.%d\n", + driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff); + + // Load the configuration file for the device. + loadConfigurationLocked(device); + + // Figure out the kinds of events the device reports. + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask); + ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask); + ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask); + ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask); + ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask); + ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask); + ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask); + + // See if this is a keyboard. Ignore everything in the button range except for + // joystick and gamepad buttons which are handled like keyboards for the most part. + bool haveKeyboardKeys = containsNonZeroByte(device->keyBitmask, 0, sizeof_bit_array(BTN_MISC)) + || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK), + sizeof_bit_array(KEY_MAX + 1)); + bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_MISC), + sizeof_bit_array(BTN_MOUSE)) + || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_JOYSTICK), + sizeof_bit_array(BTN_DIGI)); + if (haveKeyboardKeys || haveGamepadButtons) { + device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; + } + + // See if this is a cursor device such as a trackball or mouse. + if (test_bit(BTN_MOUSE, device->keyBitmask) + && test_bit(REL_X, device->relBitmask) + && test_bit(REL_Y, device->relBitmask)) { + device->classes |= INPUT_DEVICE_CLASS_CURSOR; + } + + // See if this is a touch pad. + // Is this a new modern multi-touch driver? + if (test_bit(ABS_MT_POSITION_X, device->absBitmask) + && test_bit(ABS_MT_POSITION_Y, device->absBitmask)) { + // Some joysticks such as the PS3 controller report axes that conflict + // with the ABS_MT range. Try to confirm that the device really is + // a touch screen. + if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) { + device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT; + } + // Is this an old style single-touch driver? + } else if (test_bit(BTN_TOUCH, device->keyBitmask) + && test_bit(ABS_X, device->absBitmask) + && test_bit(ABS_Y, device->absBitmask)) { + device->classes |= INPUT_DEVICE_CLASS_TOUCH; + } + + // See if this device is a joystick. + // Assumes that joysticks always have gamepad buttons in order to distinguish them + // from other devices such as accelerometers that also have absolute axes. + if (haveGamepadButtons) { + uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK; + for (int i = 0; i <= ABS_MAX; i++) { + if (test_bit(i, device->absBitmask) + && (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) { + device->classes = assumedClasses; + break; + } + } + } + + // Check whether this device has switches. + for (int i = 0; i <= SW_MAX; i++) { + if (test_bit(i, device->swBitmask)) { + device->classes |= INPUT_DEVICE_CLASS_SWITCH; + break; + } + } + + // Check whether this device supports the vibrator. + if (test_bit(FF_RUMBLE, device->ffBitmask)) { + device->classes |= INPUT_DEVICE_CLASS_VIBRATOR; + } + + // Configure virtual keys. + if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) { + // Load the virtual keys for the touch screen, if any. + // We do this now so that we can make sure to load the keymap if necessary. + status_t status = loadVirtualKeyMapLocked(device); + if (!status) { + device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; + } + } + + // Load the key map. + // We need to do this for joysticks too because the key layout may specify axes. + status_t keyMapStatus = NAME_NOT_FOUND; + if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) { + // Load the keymap for the device. + keyMapStatus = loadKeyMapLocked(device); + } + + // Configure the keyboard, gamepad or virtual keyboard. + if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) { + // Register the keyboard as a built-in keyboard if it is eligible. + if (!keyMapStatus + && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD + && isEligibleBuiltInKeyboard(device->identifier, + device->configuration, &device->keyMap)) { + mBuiltInKeyboardId = device->id; + } + + // 'Q' key support = cheap test of whether this is an alpha-capable kbd + if (hasKeycodeLocked(device, AKEYCODE_Q)) { + device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY; + } + + // See if this device has a DPAD. + if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) && + hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) && + hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) && + hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) && + hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) { + device->classes |= INPUT_DEVICE_CLASS_DPAD; + } + + // See if this device has a gamepad. + for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) { + if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) { + device->classes |= INPUT_DEVICE_CLASS_GAMEPAD; + break; + } + } + + // Disable kernel key repeat since we handle it ourselves + unsigned int repeatRate[] = {0,0}; + if (ioctl(fd, EVIOCSREP, repeatRate)) { + ALOGW("Unable to disable kernel key repeat for %s: %s", devicePath, strerror(errno)); + } + } + + // If the device isn't recognized as something we handle, don't monitor it. + if (device->classes == 0) { + ALOGV("Dropping device: id=%d, path='%s', name='%s'", + deviceId, devicePath, device->identifier.name.string()); + delete device; + return -1; + } + + // Determine whether the device is external or internal. + if (isExternalDeviceLocked(device)) { + device->classes |= INPUT_DEVICE_CLASS_EXTERNAL; + } + + if (device->classes & (INPUT_DEVICE_CLASS_JOYSTICK | INPUT_DEVICE_CLASS_GAMEPAD)) { + device->controllerNumber = getNextControllerNumberLocked(device); + setLedForController(device); + } + + // Register with epoll. + struct epoll_event eventItem; + memset(&eventItem, 0, sizeof(eventItem)); + eventItem.events = mUsingEpollWakeup ? EPOLLIN : EPOLLIN | EPOLLWAKEUP; + eventItem.data.u32 = deviceId; + if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) { + ALOGE("Could not add device fd to epoll instance. errno=%d", errno); + delete device; + return -1; + } + + String8 wakeMechanism("EPOLLWAKEUP"); + if (!mUsingEpollWakeup) { +#ifndef EVIOCSSUSPENDBLOCK + // uapi headers don't include EVIOCSSUSPENDBLOCK, and future kernels + // will use an epoll flag instead, so as long as we want to support + // this feature, we need to be prepared to define the ioctl ourselves. +#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int) +#endif + if (ioctl(fd, EVIOCSSUSPENDBLOCK, 1)) { + wakeMechanism = ""; + } else { + wakeMechanism = "EVIOCSSUSPENDBLOCK"; + } + } + + // Tell the kernel that we want to use the monotonic clock for reporting timestamps + // associated with input events. This is important because the input system + // uses the timestamps extensively and assumes they were recorded using the monotonic + // clock. + // + // In older kernel, before Linux 3.4, there was no way to tell the kernel which + // clock to use to input event timestamps. The standard kernel behavior was to + // record a real time timestamp, which isn't what we want. Android kernels therefore + // contained a patch to the evdev_event() function in drivers/input/evdev.c to + // replace the call to do_gettimeofday() with ktime_get_ts() to cause the monotonic + // clock to be used instead of the real time clock. + // + // As of Linux 3.4, there is a new EVIOCSCLOCKID ioctl to set the desired clock. + // Therefore, we no longer require the Android-specific kernel patch described above + // as long as we make sure to set select the monotonic clock. We do that here. + int clockId = CLOCK_MONOTONIC; + bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId); + + ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, " + "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, " + "wakeMechanism=%s, usingClockIoctl=%s", + deviceId, fd, devicePath, device->identifier.name.string(), + device->classes, + device->configurationFile.string(), + device->keyMap.keyLayoutFile.string(), + device->keyMap.keyCharacterMapFile.string(), + toString(mBuiltInKeyboardId == deviceId), + wakeMechanism.string(), toString(usingClockIoctl)); + + addDeviceLocked(device); + return 0; +} + +void EventHub::createVirtualKeyboardLocked() { + InputDeviceIdentifier identifier; + identifier.name = "Virtual"; + identifier.uniqueId = ""; + assignDescriptorLocked(identifier); + + Device* device = new Device(-1, VIRTUAL_KEYBOARD_ID, String8(""), identifier); + device->classes = INPUT_DEVICE_CLASS_KEYBOARD + | INPUT_DEVICE_CLASS_ALPHAKEY + | INPUT_DEVICE_CLASS_DPAD + | INPUT_DEVICE_CLASS_VIRTUAL; + loadKeyMapLocked(device); + addDeviceLocked(device); +} + +void EventHub::addDeviceLocked(Device* device) { + mDevices.add(device->id, device); + device->next = mOpeningDevices; + mOpeningDevices = device; +} + +void EventHub::loadConfigurationLocked(Device* device) { + device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier( + device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION); + if (device->configurationFile.isEmpty()) { + ALOGD("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) { + ALOGE("Error loading input device configuration file for device '%s'. " + "Using default configuration.", + device->identifier.name.string()); + } + } +} + +status_t EventHub::loadVirtualKeyMapLocked(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::loadKeyMapLocked(Device* device) { + return device->keyMap.load(device->identifier, device->configuration); +} + +bool EventHub::isExternalDeviceLocked(Device* device) { + if (device->configuration) { + bool value; + if (device->configuration->tryGetProperty(String8("device.internal"), value)) { + return !value; + } + } + return device->identifier.bus == BUS_USB || device->identifier.bus == BUS_BLUETOOTH; +} + +int32_t EventHub::getNextControllerNumberLocked(Device* device) { + if (mControllerNumbers.isFull()) { + ALOGI("Maximum number of controllers reached, assigning controller number 0 to device %s", + device->identifier.name.string()); + return 0; + } + // Since the controller number 0 is reserved for non-controllers, translate all numbers up by + // one + return static_cast(mControllerNumbers.markFirstUnmarkedBit() + 1); +} + +void EventHub::releaseControllerNumberLocked(Device* device) { + int32_t num = device->controllerNumber; + device->controllerNumber= 0; + if (num == 0) { + return; + } + mControllerNumbers.clearBit(static_cast(num - 1)); +} + +void EventHub::setLedForController(Device* device) { + for (int i = 0; i < MAX_CONTROLLER_LEDS; i++) { + setLedStateLocked(device, ALED_CONTROLLER_1 + i, device->controllerNumber == i + 1); + } +} + +bool EventHub::hasKeycodeLocked(Device* device, int keycode) const { + if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) { + return false; + } + + Vector scanCodes; + device->keyMap.keyLayoutMap->findScanCodesForKey(keycode, &scanCodes); + const size_t N = scanCodes.size(); + for (size_t i=0; i= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) { + return true; + } + } + + return false; +} + +status_t EventHub::mapLed(Device* device, int32_t led, int32_t* outScanCode) const { + if (!device->keyMap.haveKeyLayout() || !device->ledBitmask) { + return NAME_NOT_FOUND; + } + + int32_t scanCode; + if(device->keyMap.keyLayoutMap->findScanCodeForLed(led, &scanCode) != NAME_NOT_FOUND) { + if(scanCode >= 0 && scanCode <= LED_MAX && test_bit(scanCode, device->ledBitmask)) { + *outScanCode = scanCode; + return NO_ERROR; + } + } + return NAME_NOT_FOUND; +} + +status_t EventHub::closeDeviceByPathLocked(const char *devicePath) { + Device* device = getDeviceByPathLocked(devicePath); + if (device) { + closeDeviceLocked(device); + return 0; + } + ALOGV("Remove device: %s not found, device may already have been removed.", devicePath); + return -1; +} + +void EventHub::closeAllDevicesLocked() { + while (mDevices.size() > 0) { + closeDeviceLocked(mDevices.valueAt(mDevices.size() - 1)); + } +} + +void EventHub::closeDeviceLocked(Device* device) { + ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n", + device->path.string(), device->identifier.name.string(), device->id, + device->fd, device->classes); + + if (device->id == mBuiltInKeyboardId) { + ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this", + device->path.string(), mBuiltInKeyboardId); + mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD; + } + + if (!device->isVirtual()) { + if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, device->fd, NULL)) { + ALOGW("Could not remove device fd from epoll instance. errno=%d", errno); + } + } + + releaseControllerNumberLocked(device); + + mDevices.removeItem(device->id); + device->close(); + + // Unlink for opening devices list if it is present. + Device* pred = NULL; + bool found = false; + for (Device* entry = mOpeningDevices; entry != NULL; ) { + if (entry == device) { + found = true; + break; + } + pred = entry; + entry = entry->next; + } + if (found) { + // Unlink the device from the opening devices list then delete it. + // We don't need to tell the client that the device was closed because + // it does not even know it was opened in the first place. + ALOGI("Device %s was immediately closed after opening.", device->path.string()); + if (pred) { + pred->next = device->next; + } else { + mOpeningDevices = device->next; + } + delete device; + } else { + // Link into closing devices list. + // The device will be deleted later after we have informed the client. + device->next = mClosingDevices; + mClosingDevices = device; + } +} + +status_t EventHub::readNotifyLocked() { + int res; + char devname[PATH_MAX]; + char *filename; + char event_buf[512]; + int event_size; + int event_pos = 0; + struct inotify_event *event; + + ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd); + res = read(mINotifyFd, event_buf, sizeof(event_buf)); + if(res < (int)sizeof(*event)) { + if(errno == EINTR) + return 0; + ALOGW("could not get event, %s\n", strerror(errno)); + return -1; + } + //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) { + openDeviceLocked(devname); + } else { + ALOGI("Removing device '%s' due to inotify event\n", devname); + closeDeviceByPathLocked(devname); + } + } + event_size = sizeof(*event) + event->len; + res -= event_size; + event_pos += event_size; + } + return 0; +} + +status_t EventHub::scanDirLocked(const char *dirname) +{ + char devname[PATH_MAX]; + char *filename; + DIR *dir; + struct dirent *de; + dir = opendir(dirname); + if(dir == 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); + openDeviceLocked(devname); + } + closedir(dir); + return 0; +} + +void EventHub::requestReopenDevices() { + ALOGV("requestReopenDevices() called"); + + AutoMutex _l(mLock); + mNeedToReopenDevices = true; +} + +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 = 0; i < mDevices.size(); i++) { + const Device* device = mDevices.valueAt(i); + 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 "Descriptor: %s\n", device->identifier.descriptor.string()); + dump.appendFormat(INDENT3 "Location: %s\n", device->identifier.location.string()); + dump.appendFormat(INDENT3 "ControllerNumber: %d\n", device->controllerNumber); + 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()); + dump.appendFormat(INDENT3 "HaveKeyboardLayoutOverlay: %s\n", + toString(device->overlayKeyMap != NULL)); + } + } // release lock +} + +void EventHub::monitor() { + // Acquire and release the lock to ensure that the event hub has not deadlocked. + mLock.lock(); + mLock.unlock(); +} + + +}; // namespace android diff --git a/services/inputflinger/EventHub.h b/services/inputflinger/EventHub.h new file mode 100644 index 000000000..20179aef2 --- /dev/null +++ b/services/inputflinger/EventHub.h @@ -0,0 +1,457 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Convenience constants. */ + +#define BTN_FIRST 0x100 // first button code +#define BTN_LAST 0x15f // last button code + +/* + * These constants are used privately in Android to pass raw timestamps + * through evdev from uinput device drivers because there is currently no + * other way to transfer this information. The evdev driver automatically + * timestamps all input events with the time they were posted and clobbers + * whatever information was passed in. + * + * For the purposes of this hack, the timestamp is specified in the + * CLOCK_MONOTONIC timebase and is split into two EV_MSC events specifying + * seconds and microseconds. + */ +#define MSC_ANDROID_TIME_SEC 0x6 +#define MSC_ANDROID_TIME_USEC 0x7 + +namespace android { + +enum { + // Device id of a special "virtual" keyboard that is always present. + VIRTUAL_KEYBOARD_ID = -1, + // Device id of the "built-in" keyboard if there is one. + BUILT_IN_KEYBOARD_ID = 0, +}; + +/* + * A raw event as retrieved from the EventHub. + */ +struct RawEvent { + nsecs_t when; + int32_t deviceId; + int32_t type; + int32_t code; + int32_t value; +}; + +/* Describes an absolute axis. */ +struct RawAbsoluteAxisInfo { + bool valid; // true if the information is valid, false otherwise + + int32_t minValue; // minimum value + int32_t maxValue; // maximum value + int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8 + int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise + int32_t resolution; // resolution in units per mm or radians per mm + + inline void clear() { + valid = false; + minValue = 0; + maxValue = 0; + flat = 0; + fuzz = 0; + resolution = 0; + } +}; + +/* + * Input device classes. + */ +enum { + /* The input device is a keyboard or has buttons. */ + INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001, + + /* The input device is an alpha-numeric keyboard (not just a dial pad). */ + INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002, + + /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */ + INPUT_DEVICE_CLASS_TOUCH = 0x00000004, + + /* The input device is a cursor device such as a trackball or mouse. */ + INPUT_DEVICE_CLASS_CURSOR = 0x00000008, + + /* The input device is a multi-touch touchscreen. */ + INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010, + + /* The input device is a directional pad (implies keyboard, has DPAD keys). */ + INPUT_DEVICE_CLASS_DPAD = 0x00000020, + + /* The input device is a gamepad (implies keyboard, has BUTTON keys). */ + INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040, + + /* The input device has switches. */ + INPUT_DEVICE_CLASS_SWITCH = 0x00000080, + + /* The input device is a joystick (implies gamepad, has joystick absolute axes). */ + INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100, + + /* The input device has a vibrator (supports FF_RUMBLE). */ + INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200, + + /* The input device is virtual (not a real device, not part of UI configuration). */ + INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000, + + /* The input device is external (not built-in). */ + INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000, +}; + +/* + * Gets the class that owns an axis, in cases where multiple classes might claim + * the same axis for different purposes. + */ +extern uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses); + +/* + * Grand Central Station for events. + * + * The event hub aggregates input events received across all known input + * devices on the system, including devices that may be emulated by the simulator + * environment. In addition, the event hub generates fake input events to indicate + * when devices are added or removed. + * + * The event hub provides a stream of input events (via the getEvent function). + * It also supports querying the current actual state of input devices such as identifying + * which keys are currently down. Finally, the event hub keeps track of the capabilities of + * individual input devices, such as their class and the set of key codes that they support. + */ +class EventHubInterface : public 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, + + FIRST_SYNTHETIC_EVENT = DEVICE_ADDED, + }; + + virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0; + + virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const = 0; + + virtual int32_t getDeviceControllerNumber(int32_t deviceId) const = 0; + + virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0; + + virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const = 0; + + virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0; + + virtual bool hasInputProperty(int32_t deviceId, int property) const = 0; + + virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, + int32_t* outKeycode, uint32_t* outFlags) const = 0; + + virtual status_t mapAxis(int32_t deviceId, int32_t scanCode, + AxisInfo* outAxisInfo) const = 0; + + // Sets devices that are excluded from opening. + // This can be used to ignore input devices for sensors. + virtual void setExcludedDevices(const Vector& devices) = 0; + + /* + * Wait for events to become available and returns them. + * After returning, the EventHub holds onto a wake lock until the next call to getEvent. + * This ensures that the device will not go to sleep while the event is being processed. + * If the device needs to remain awake longer than that, then the caller is responsible + * for taking care of it (say, by poking the power manager user activity timer). + * + * The timeout is advisory only. If the device is asleep, it will not wake just to + * service the timeout. + * + * Returns the number of events obtained, or 0 if the timeout expired. + */ + virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0; + + /* + * Query current input state. + */ + virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0; + virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const = 0; + + /* + * Examine key input devices for specific framework keycode support + */ + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + uint8_t* outFlags) const = 0; + + virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0; + + /* LED related functions expect Android LED constants, not scan codes or HID usages */ + virtual bool hasLed(int32_t deviceId, int32_t led) const = 0; + virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0; + + virtual void getVirtualKeyDefinitions(int32_t deviceId, + Vector& outVirtualKeys) const = 0; + + virtual sp getKeyCharacterMap(int32_t deviceId) const = 0; + virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp& map) = 0; + + /* Control the vibrator. */ + virtual void vibrate(int32_t deviceId, nsecs_t duration) = 0; + virtual void cancelVibrate(int32_t deviceId) = 0; + + /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */ + virtual void requestReopenDevices() = 0; + + /* Wakes up getEvents() if it is blocked on a read. */ + virtual void wake() = 0; + + /* Dump EventHub state to a string. */ + virtual void dump(String8& dump) = 0; + + /* Called by the heatbeat to ensures that the reader has not deadlocked. */ + virtual void monitor() = 0; +}; + +class EventHub : public EventHubInterface +{ +public: + EventHub(); + + virtual uint32_t getDeviceClasses(int32_t deviceId) const; + + virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const; + + virtual int32_t getDeviceControllerNumber(int32_t deviceId) const; + + virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const; + + virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const; + + virtual bool hasRelativeAxis(int32_t deviceId, int axis) const; + + virtual bool hasInputProperty(int32_t deviceId, int property) const; + + virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, + int32_t* outKeycode, uint32_t* outFlags) const; + + virtual status_t mapAxis(int32_t deviceId, int32_t scanCode, + AxisInfo* outAxisInfo) const; + + virtual void setExcludedDevices(const Vector& devices); + + virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const; + virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const; + virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const; + + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const; + + virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize); + + virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const; + virtual bool hasLed(int32_t deviceId, int32_t led) const; + virtual void setLedState(int32_t deviceId, int32_t led, bool on); + + virtual void getVirtualKeyDefinitions(int32_t deviceId, + Vector& outVirtualKeys) const; + + virtual sp getKeyCharacterMap(int32_t deviceId) const; + virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp& map); + + virtual void vibrate(int32_t deviceId, nsecs_t duration); + virtual void cancelVibrate(int32_t deviceId); + + virtual void requestReopenDevices(); + + virtual void wake(); + + virtual void dump(String8& dump); + virtual void monitor(); + +protected: + virtual ~EventHub(); + +private: + struct Device { + Device* next; + + int fd; // may be -1 if device is virtual + const int32_t id; + const String8 path; + const InputDeviceIdentifier identifier; + + uint32_t classes; + + uint8_t keyBitmask[(KEY_MAX + 1) / 8]; + uint8_t absBitmask[(ABS_MAX + 1) / 8]; + uint8_t relBitmask[(REL_MAX + 1) / 8]; + uint8_t swBitmask[(SW_MAX + 1) / 8]; + uint8_t ledBitmask[(LED_MAX + 1) / 8]; + uint8_t ffBitmask[(FF_MAX + 1) / 8]; + uint8_t propBitmask[(INPUT_PROP_MAX + 1) / 8]; + + String8 configurationFile; + PropertyMap* configuration; + VirtualKeyMap* virtualKeyMap; + KeyMap keyMap; + + sp overlayKeyMap; + sp combinedKeyMap; + + bool ffEffectPlaying; + int16_t ffEffectId; // initially -1 + + int32_t controllerNumber; + + int32_t timestampOverrideSec; + int32_t timestampOverrideUsec; + + Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier); + ~Device(); + + void close(); + + inline bool isVirtual() const { return fd < 0; } + + const sp& getKeyCharacterMap() const { + if (combinedKeyMap != NULL) { + return combinedKeyMap; + } + return keyMap.keyCharacterMap; + } + }; + + status_t openDeviceLocked(const char *devicePath); + void createVirtualKeyboardLocked(); + void addDeviceLocked(Device* device); + void assignDescriptorLocked(InputDeviceIdentifier& identifier); + + status_t closeDeviceByPathLocked(const char *devicePath); + void closeDeviceLocked(Device* device); + void closeAllDevicesLocked(); + + status_t scanDirLocked(const char *dirname); + void scanDevicesLocked(); + status_t readNotifyLocked(); + + Device* getDeviceByDescriptorLocked(String8& descriptor) const; + Device* getDeviceLocked(int32_t deviceId) const; + Device* getDeviceByPathLocked(const char* devicePath) const; + + bool hasKeycodeLocked(Device* device, int keycode) const; + + void loadConfigurationLocked(Device* device); + status_t loadVirtualKeyMapLocked(Device* device); + status_t loadKeyMapLocked(Device* device); + + bool isExternalDeviceLocked(Device* device); + + int32_t getNextControllerNumberLocked(Device* device); + void releaseControllerNumberLocked(Device* device); + void setLedForController(Device* device); + + status_t mapLed(Device* device, int32_t led, int32_t* outScanCode) const; + void setLedStateLocked(Device* device, int32_t led, bool on); + + // Protect all internal state. + mutable Mutex mLock; + + // The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none. + // EventHub remaps the built-in keyboard to id 0 externally as required by the API. + enum { + // Must not conflict with any other assigned device ids, including + // the virtual keyboard id (-1). + NO_BUILT_IN_KEYBOARD = -2, + }; + int32_t mBuiltInKeyboardId; + + int32_t mNextDeviceId; + + BitSet32 mControllerNumbers; + + KeyedVector mDevices; + + Device *mOpeningDevices; + Device *mClosingDevices; + + bool mNeedToSendFinishedDeviceScan; + bool mNeedToReopenDevices; + bool mNeedToScanDevices; + Vector mExcludedDevices; + + int mEpollFd; + int mINotifyFd; + int mWakeReadPipeFd; + int mWakeWritePipeFd; + + // Ids used for epoll notifications not associated with devices. + static const uint32_t EPOLL_ID_INOTIFY = 0x80000001; + static const uint32_t EPOLL_ID_WAKE = 0x80000002; + + // Epoll FD list size hint. + static const int EPOLL_SIZE_HINT = 8; + + // Maximum number of signalled FDs to handle at a time. + static const int EPOLL_MAX_EVENTS = 16; + + // The array of pending epoll events and the index of the next event to be handled. + struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS]; + size_t mPendingEventCount; + size_t mPendingEventIndex; + bool mPendingINotify; + + bool mUsingEpollWakeup; +}; + +}; // namespace android + +#endif // _RUNTIME_EVENT_HUB_H diff --git a/services/inputflinger/InputApplication.cpp b/services/inputflinger/InputApplication.cpp new file mode 100644 index 000000000..a99e637fd --- /dev/null +++ b/services/inputflinger/InputApplication.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 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 "InputApplication" + +#include "InputApplication.h" + +#include + +namespace android { + +// --- InputApplicationHandle --- + +InputApplicationHandle::InputApplicationHandle() : + mInfo(NULL) { +} + +InputApplicationHandle::~InputApplicationHandle() { + delete mInfo; +} + +void InputApplicationHandle::releaseInfo() { + if (mInfo) { + delete mInfo; + mInfo = NULL; + } +} + +} // namespace android diff --git a/services/inputflinger/InputApplication.h b/services/inputflinger/InputApplication.h new file mode 100644 index 000000000..1f5504c2d --- /dev/null +++ b/services/inputflinger/InputApplication.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 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_APPLICATION_H +#define _UI_INPUT_APPLICATION_H + +#include + +#include +#include +#include + +namespace android { + +/* + * Describes the properties of an application that can receive input. + */ +struct InputApplicationInfo { + String8 name; + nsecs_t dispatchingTimeout; +}; + + +/* + * Handle for an application that can receive input. + * + * Used by the native input dispatcher as a handle for the window manager objects + * that describe an application. + */ +class InputApplicationHandle : public RefBase { +public: + inline const InputApplicationInfo* getInfo() const { + return mInfo; + } + + inline String8 getName() const { + return mInfo ? mInfo->name : String8(""); + } + + inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const { + return mInfo ? mInfo->dispatchingTimeout : defaultValue; + } + + /** + * Requests that the state of this object be updated to reflect + * the most current available information about the application. + * + * This method should only be called from within the input dispatcher's + * critical section. + * + * Returns true on success, or false if the handle is no longer valid. + */ + virtual bool updateInfo() = 0; + + /** + * Releases the storage used by the associated information when it is + * no longer needed. + */ + void releaseInfo(); + +protected: + InputApplicationHandle(); + virtual ~InputApplicationHandle(); + + InputApplicationInfo* mInfo; +}; + +} // namespace android + +#endif // _UI_INPUT_APPLICATION_H diff --git a/services/inputflinger/InputDispatcher.cpp b/services/inputflinger/InputDispatcher.cpp new file mode 100644 index 000000000..c00046075 --- /dev/null +++ b/services/inputflinger/InputDispatcher.cpp @@ -0,0 +1,4473 @@ +/* + * 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 ATRACE_TAG ATRACE_TAG_INPUT + +//#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 the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 0 + +// Log debug messages about registrations. +#define DEBUG_REGISTRATION 0 + +// Log debug messages about input event injection. +#define DEBUG_INJECTION 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 + +// Log debug messages about hover events. +#define DEBUG_HOVER 0 + +#include "InputDispatcher.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define INDENT " " +#define INDENT2 " " +#define INDENT3 " " +#define INDENT4 " " + +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 + +// Amount of time to allow for an event to be dispatched (measured since its eventTime) +// before considering it stale and dropping it. +const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec + +// Amount of time to allow touch events to be streamed out to a connection before requiring +// that the first event be finished. This value extends the ANR timeout by the specified +// amount. For example, if streaming is allowed to get ahead by one second relative to the +// queue of waiting unfinished events, then ANRs will similarly be delayed by one second. +const nsecs_t STREAM_AHEAD_EVENT_TIMEOUT = 500 * 1000000LL; // 0.5sec + +// Log a warning when an event takes longer than this to process, even if an ANR does not occur. +const nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec + +// Number of recent events to keep for debugging purposes. +const size_t RECENT_QUEUE_MAX_SIZE = 10; + +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)) { + ALOGE("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: + case AMOTION_EVENT_ACTION_HOVER_ENTER: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + case AMOTION_EVENT_ACTION_HOVER_EXIT: + case AMOTION_EVENT_ACTION_SCROLL: + 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 PointerProperties* pointerProperties) { + if (! isValidMotionAction(action, pointerCount)) { + ALOGE("Motion event has invalid action code 0x%x", action); + return false; + } + if (pointerCount < 1 || pointerCount > MAX_POINTERS) { + ALOGE("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 = pointerProperties[i].id; + if (id < 0 || id > MAX_POINTER_ID) { + ALOGE("Motion event has invalid pointer id %d; value must be between 0 and %d", + id, MAX_POINTER_ID); + return false; + } + if (pointerIdBits.hasBit(id)) { + ALOGE("Motion event has duplicate pointer id %d", id); + return false; + } + pointerIdBits.markBit(id); + } + return true; +} + +static bool isMainDisplay(int32_t displayId) { + return displayId == ADISPLAY_ID_DEFAULT || displayId == ADISPLAY_ID_NONE; +} + +static void dumpRegion(String8& dump, const Region& region) { + if (region.isEmpty()) { + dump.append(""); + return; + } + + bool first = true; + Region::const_iterator cur = region.begin(); + Region::const_iterator const tail = region.end(); + while (cur != tail) { + if (first) { + first = false; + } else { + dump.append("|"); + } + dump.appendFormat("[%d,%d][%d,%d]", cur->left, cur->top, cur->right, cur->bottom); + cur++; + } +} + + +// --- InputDispatcher --- + +InputDispatcher::InputDispatcher(const sp& policy) : + mPolicy(policy), + mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX), + mNextUnblockedEvent(NULL), + mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false), + mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) { + mLooper = new Looper(false); + + mKeyRepeatState.lastKeyEntry = NULL; + + policy->getDispatcherConfiguration(&mConfig); +} + +InputDispatcher::~InputDispatcher() { + { // acquire lock + AutoMutex _l(mLock); + + resetKeyRepeatLocked(); + releasePendingEventLocked(); + drainInboundQueueLocked(); + } + + while (mConnectionsByFd.size() != 0) { + unregisterInputChannel(mConnectionsByFd.valueAt(0)->inputChannel); + } +} + +void InputDispatcher::dispatchOnce() { + nsecs_t nextWakeupTime = LONG_LONG_MAX; + { // acquire lock + AutoMutex _l(mLock); + mDispatcherIsAliveCondition.broadcast(); + + // Run a dispatch loop if there are no pending commands. + // The dispatch loop might enqueue commands to run afterwards. + if (!haveCommandsLocked()) { + dispatchOnceInnerLocked(&nextWakeupTime); + } + + // Run all pending commands if there are any. + // If any commands were run then force the next poll to wake up immediately. + if (runCommandsLockedInterruptible()) { + nextWakeupTime = LONG_LONG_MIN; + } + } // release lock + + // Wait for callback or timeout or wake. (make sure we round up, not down) + nsecs_t currentTime = now(); + int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime); + mLooper->pollOnce(timeoutMillis); +} + +void InputDispatcher::dispatchOnceInnerLocked(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 (!mPolicy->isKeyRepeatEnabled()) { + resetKeyRepeatLocked(); + } + + // If dispatching is frozen, do not process timeouts or try to deliver any new events. + if (mDispatchFrozen) { +#if DEBUG_FOCUS + ALOGD("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); + } else { + if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) { + *nextWakeupTime = mKeyRepeatState.nextRepeatTime; + } + } + } + + // Nothing to do if there is no pending event. + if (!mPendingEvent) { + return; + } + } else { + // Inbound queue has at least one entry. + mPendingEvent = mInboundQueue.dequeueAtHead(); + traceInboundQueueLengthLocked(); + } + + // Poke user activity for this event. + if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) { + pokeUserActivityLocked(mPendingEvent); + } + + // Get ready to dispatch the event. + resetANRTimeoutsLocked(); + } + + // Now we have an event to dispatch. + // All events are eventually dequeued and processed this way, even if we intend to drop them. + ALOG_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; + } + + if (mNextUnblockedEvent == mPendingEvent) { + mNextUnblockedEvent = NULL; + } + + switch (mPendingEvent->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: { + ConfigurationChangedEntry* typedEntry = + static_cast(mPendingEvent); + done = dispatchConfigurationChangedLocked(currentTime, typedEntry); + dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped + break; + } + + case EventEntry::TYPE_DEVICE_RESET: { + DeviceResetEntry* typedEntry = + static_cast(mPendingEvent); + done = dispatchDeviceResetLocked(currentTime, typedEntry); + dropReason = DROP_REASON_NOT_DROPPED; // device resets are never dropped + break; + } + + case EventEntry::TYPE_KEY: { + KeyEntry* typedEntry = static_cast(mPendingEvent); + if (isAppSwitchDue) { + if (isAppSwitchKeyEventLocked(typedEntry)) { + resetPendingAppSwitchLocked(true); + isAppSwitchDue = false; + } else if (dropReason == DROP_REASON_NOT_DROPPED) { + dropReason = DROP_REASON_APP_SWITCH; + } + } + if (dropReason == DROP_REASON_NOT_DROPPED + && isStaleEventLocked(currentTime, typedEntry)) { + dropReason = DROP_REASON_STALE; + } + if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) { + dropReason = DROP_REASON_BLOCKED; + } + done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime); + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* typedEntry = static_cast(mPendingEvent); + if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) { + dropReason = DROP_REASON_APP_SWITCH; + } + if (dropReason == DROP_REASON_NOT_DROPPED + && isStaleEventLocked(currentTime, typedEntry)) { + dropReason = DROP_REASON_STALE; + } + if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) { + dropReason = DROP_REASON_BLOCKED; + } + done = dispatchMotionLocked(currentTime, typedEntry, + &dropReason, nextWakeupTime); + break; + } + + default: + ALOG_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); + traceInboundQueueLengthLocked(); + + switch (entry->type) { + case EventEntry::TYPE_KEY: { + // Optimize app switch latency. + // If the application takes too long to catch up then we drop all events preceding + // the app switch key. + KeyEntry* keyEntry = static_cast(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 + ALOGD("App switch is pending!"); +#endif + mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT; + mAppSwitchSawKeyDown = false; + needWake = true; + } + } + } + break; + } + + case EventEntry::TYPE_MOTION: { + // Optimize case where the current application is unresponsive and the user + // decides to touch a window in a different application. + // If the application takes too long to catch up then we drop all events preceding + // the touch into the other window. + MotionEntry* motionEntry = static_cast(entry); + if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN + && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) + && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY + && mInputTargetWaitApplicationHandle != NULL) { + int32_t displayId = motionEntry->displayId; + int32_t x = int32_t(motionEntry->pointerCoords[0]. + getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(motionEntry->pointerCoords[0]. + getAxisValue(AMOTION_EVENT_AXIS_Y)); + sp touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y); + if (touchedWindowHandle != NULL + && touchedWindowHandle->inputApplicationHandle + != mInputTargetWaitApplicationHandle) { + // User touched a different application than the one we are waiting on. + // Flag the event, and start pruning the input queue. + mNextUnblockedEvent = motionEntry; + needWake = true; + } + } + break; + } + } + + return needWake; +} + +void InputDispatcher::addRecentEventLocked(EventEntry* entry) { + entry->refCount += 1; + mRecentQueue.enqueueAtTail(entry); + if (mRecentQueue.count() > RECENT_QUEUE_MAX_SIZE) { + mRecentQueue.dequeueAtHead()->release(); + } +} + +sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, + int32_t x, int32_t y) { + // Traverse windows from front to back to find touched window. + size_t numWindows = mWindowHandles.size(); + for (size_t i = 0; i < numWindows; i++) { + sp windowHandle = mWindowHandles.itemAt(i); + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + if (windowInfo->displayId == displayId) { + int32_t flags = windowInfo->layoutParamsFlags; + int32_t privateFlags = windowInfo->layoutParamsPrivateFlags; + + if (windowInfo->visible) { + if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) { + bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE + | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0; + if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) { + // Found window. + return windowHandle; + } + } + } + + if (privateFlags & InputWindowInfo::PRIVATE_FLAG_SYSTEM_ERROR) { + // Error window is on top but not visible, so touch is dropped. + return NULL; + } + } + } + return NULL; +} + +void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) { + const char* reason; + switch (dropReason) { + case DROP_REASON_POLICY: +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("Dropped event because policy consumed it."); +#endif + reason = "inbound event was dropped because the policy consumed it"; + break; + case DROP_REASON_DISABLED: + ALOGI("Dropped event because input dispatch is disabled."); + reason = "inbound event was dropped because input dispatch is disabled"; + break; + case DROP_REASON_APP_SWITCH: + ALOGI("Dropped event because of pending overdue app switch."); + reason = "inbound event was dropped because of pending overdue app switch"; + break; + case DROP_REASON_BLOCKED: + ALOGI("Dropped event because the current application is not responding and the user " + "has started interacting with a different application."); + reason = "inbound event was dropped because the current application is not responding " + "and the user has started interacting with a different application"; + break; + case DROP_REASON_STALE: + ALOGI("Dropped event because it is stale."); + reason = "inbound event was dropped because it is stale"; + break; + default: + ALOG_ASSERT(false); + return; + } + + switch (entry->type) { + case EventEntry::TYPE_KEY: { + CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); + break; + } + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast(entry); + if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) { + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); + } else { + CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); + } + break; + } + } +} + +bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) { + return keyCode == AKEYCODE_HOME + || keyCode == AKEYCODE_ENDCALL + || keyCode == AKEYCODE_APP_SWITCH; +} + +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) { + ALOGD("App switch has arrived."); + } else { + ALOGD("App switch was abandoned."); + } +#endif +} + +bool InputDispatcher::isStaleEventLocked(nsecs_t currentTime, EventEntry* entry) { + return currentTime - entry->eventTime >= STALE_EVENT_TIMEOUT; +} + +bool InputDispatcher::haveCommandsLocked() const { + return !mCommandQueue.isEmpty(); +} + +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(); + delete commandEntry; + } while (! mCommandQueue.isEmpty()); + return true; +} + +InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) { + CommandEntry* commandEntry = new CommandEntry(command); + mCommandQueue.enqueueAtTail(commandEntry); + return commandEntry; +} + +void InputDispatcher::drainInboundQueueLocked() { + while (! mInboundQueue.isEmpty()) { + EventEntry* entry = mInboundQueue.dequeueAtHead(); + releaseInboundEventLocked(entry); + } + traceInboundQueueLengthLocked(); +} + +void InputDispatcher::releasePendingEventLocked() { + if (mPendingEvent) { + resetANRTimeoutsLocked(); + 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 + ALOGD("Injected inbound event was dropped."); +#endif + setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED); + } + if (entry == mNextUnblockedEvent) { + mNextUnblockedEvent = NULL; + } + addRecentEventLocked(entry); + entry->release(); +} + +void InputDispatcher::resetKeyRepeatLocked() { + if (mKeyRepeatState.lastKeyEntry) { + mKeyRepeatState.lastKeyEntry->release(); + mKeyRepeatState.lastKeyEntry = NULL; + } +} + +InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(nsecs_t currentTime) { + 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) { + entry->recycle(); + entry->eventTime = currentTime; + entry->policyFlags = policyFlags; + entry->repeatCount += 1; + } else { + KeyEntry* newEntry = new KeyEntry(currentTime, + entry->deviceId, entry->source, policyFlags, + entry->action, entry->flags, entry->keyCode, entry->scanCode, + entry->metaState, entry->repeatCount + 1, entry->downTime); + + mKeyRepeatState.lastKeyEntry = newEntry; + entry->release(); + + 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; + + mKeyRepeatState.nextRepeatTime = currentTime + mConfig.keyRepeatDelay; + return entry; +} + +bool InputDispatcher::dispatchConfigurationChangedLocked( + nsecs_t currentTime, ConfigurationChangedEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("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::dispatchDeviceResetLocked( + nsecs_t currentTime, DeviceResetEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("dispatchDeviceReset - eventTime=%lld, deviceId=%d", entry->eventTime, entry->deviceId); +#endif + + CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, + "device was reset"); + options.deviceId = entry->deviceId; + synthesizeCancelationEventsForAllConnectionsLocked(options); + return true; +} + +bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry, + 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->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) { + 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 + mConfig.keyRepeatTimeout; + } + mKeyRepeatState.lastKeyEntry = entry; + entry->refCount += 1; + } else if (! entry->syntheticRepeat) { + resetKeyRepeatLocked(); + } + + if (entry->repeatCount == 1) { + entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS; + } else { + entry->flags &= ~AKEY_EVENT_FLAG_LONG_PRESS; + } + + entry->dispatchInProgress = true; + + logOutboundKeyDetailsLocked("dispatchKey - ", entry); + } + + // Handle case where the policy asked us to try again later last time. + if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { + if (currentTime < entry->interceptKeyWakeupTime) { + if (entry->interceptKeyWakeupTime < *nextWakeupTime) { + *nextWakeupTime = entry->interceptKeyWakeupTime; + } + return false; // wait until next wakeup + } + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + entry->interceptKeyWakeupTime = 0; + } + + // 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 (mFocusedWindowHandle != NULL) { + commandEntry->inputWindowHandle = mFocusedWindowHandle; + } + 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) { + setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY + ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED); + return true; + } + + // Identify targets. + Vector inputTargets; + int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, inputTargets, nextWakeupTime); + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } + + addMonitoringTargetsLocked(inputTargets); + + // Dispatch the key. + dispatchEventLocked(currentTime, entry, inputTargets); + return true; +} + +void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("%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; + + logOutboundMotionDetailsLocked("dispatchMotion - ", entry); + } + + // Clean up if dropping the event. + if (*dropReason != DROP_REASON_NOT_DROPPED) { + 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. + Vector inputTargets; + + bool conflictingPointerActions = false; + int32_t injectionResult; + if (isPointerEvent) { + // Pointer event. (eg. touchscreen) + injectionResult = findTouchedWindowTargetsLocked(currentTime, + entry, inputTargets, nextWakeupTime, &conflictingPointerActions); + } else { + // Non touch event. (eg. trackball) + injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, inputTargets, nextWakeupTime); + } + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } + + // TODO: support sending secondary display events to input monitors + if (isMainDisplay(entry->displayId)) { + addMonitoringTargetsLocked(inputTargets); + } + + // Dispatch the motion. + if (conflictingPointerActions) { + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + "conflicting pointer actions"); + synthesizeCancelationEventsForAllConnectionsLocked(options); + } + dispatchEventLocked(currentTime, entry, inputTargets); + return true; +} + + +void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, " + "action=0x%x, flags=0x%x, " + "metaState=0x%x, buttonState=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->buttonState, + entry->edgeFlags, entry->xPrecision, entry->yPrecision, + entry->downTime); + + for (uint32_t i = 0; i < entry->pointerCount; i++) { + ALOGD(" Pointer %d: id=%d, toolType=%d, " + "x=%f, y=%f, pressure=%f, size=%f, " + "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " + "orientation=%f", + i, entry->pointerProperties[i].id, + entry->pointerProperties[i].toolType, + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), + entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); + } +#endif +} + +void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, + EventEntry* eventEntry, const Vector& inputTargets) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("dispatchEventToCurrentInputTargets"); +#endif + + ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true + + pokeUserActivityLocked(eventEntry); + + for (size_t i = 0; i < inputTargets.size(); i++) { + const InputTarget& inputTarget = inputTargets.itemAt(i); + + ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); + if (connectionIndex >= 0) { + sp connection = mConnectionsByFd.valueAt(connectionIndex); + prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget); + } else { +#if DEBUG_FOCUS + ALOGD("Dropping event delivery to target with channel '%s' because it " + "is no longer registered with the input dispatcher.", + inputTarget.inputChannel->getName().string()); +#endif + } + } +} + +int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, + const EventEntry* entry, + const sp& applicationHandle, + const sp& windowHandle, + nsecs_t* nextWakeupTime, const char* reason) { + if (applicationHandle == NULL && windowHandle == NULL) { + if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) { +#if DEBUG_FOCUS + ALOGD("Waiting for system to become ready for input. Reason: %s", reason); +#endif + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY; + mInputTargetWaitStartTime = currentTime; + mInputTargetWaitTimeoutTime = LONG_LONG_MAX; + mInputTargetWaitTimeoutExpired = false; + mInputTargetWaitApplicationHandle.clear(); + } + } else { + if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { +#if DEBUG_FOCUS + ALOGD("Waiting for application to become ready for input: %s. Reason: %s", + getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(), + reason); +#endif + nsecs_t timeout; + if (windowHandle != NULL) { + timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); + } else if (applicationHandle != NULL) { + timeout = applicationHandle->getDispatchingTimeout( + DEFAULT_INPUT_DISPATCHING_TIMEOUT); + } else { + timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; + } + + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY; + mInputTargetWaitStartTime = currentTime; + mInputTargetWaitTimeoutTime = currentTime + timeout; + mInputTargetWaitTimeoutExpired = false; + mInputTargetWaitApplicationHandle.clear(); + + if (windowHandle != NULL) { + mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle; + } + if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) { + mInputTargetWaitApplicationHandle = applicationHandle; + } + } + } + + if (mInputTargetWaitTimeoutExpired) { + return INPUT_EVENT_INJECTION_TIMED_OUT; + } + + if (currentTime >= mInputTargetWaitTimeoutTime) { + onANRLocked(currentTime, applicationHandle, windowHandle, + entry->eventTime, mInputTargetWaitStartTime, reason); + + // 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) { + if (newTimeout > 0) { + // Extend the timeout. + mInputTargetWaitTimeoutTime = now() + newTimeout; + } else { + // Give up. + mInputTargetWaitTimeoutExpired = true; + + // Input state will not be realistic. Mark it out of sync. + if (inputChannel.get()) { + ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); + if (connectionIndex >= 0) { + sp connection = mConnectionsByFd.valueAt(connectionIndex); + sp windowHandle = connection->inputWindowHandle; + + if (windowHandle != NULL) { + mTouchState.removeWindow(windowHandle); + } + + if (connection->status == Connection::STATUS_NORMAL) { + CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, + "application not responding"); + synthesizeCancelationEventsForConnectionLocked(connection, options); + } + } + } + } +} + +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 + ALOGD("Resetting ANR timeouts."); +#endif + + // Reset input target wait timeout. + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; + mInputTargetWaitApplicationHandle.clear(); +} + +int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, + const EventEntry* entry, Vector& inputTargets, nsecs_t* nextWakeupTime) { + int32_t injectionResult; + + // If there is no currently focused window and no focused application + // then drop the event. + if (mFocusedWindowHandle == NULL) { + if (mFocusedApplicationHandle != NULL) { + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplicationHandle, NULL, nextWakeupTime, + "Waiting because no window has focus but there is a " + "focused application that may eventually add a window " + "when it finishes starting up."); + goto Unresponsive; + } + + ALOGI("Dropping event because there is no focused window or focused application."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; + } + + // Check permissions. + if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + goto Failed; + } + + // If the currently focused window is paused then keep waiting. + if (mFocusedWindowHandle->getInfo()->paused) { + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, + "Waiting because the focused window is paused."); + goto Unresponsive; + } + + // If the currently focused window is still working on previous events then keep waiting. + if (!isWindowReadyForMoreInputLocked(currentTime, mFocusedWindowHandle, entry)) { + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, + "Waiting because the focused window has not finished " + "processing the input events that were previously delivered to it."); + goto Unresponsive; + } + + // Success! Output targets. + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + addWindowTargetLocked(mFocusedWindowHandle, + InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0), + inputTargets); + + // Done. +Failed: +Unresponsive: + nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime); + updateDispatchStatisticsLocked(currentTime, entry, + injectionResult, timeSpentWaitingForApplication); +#if DEBUG_FOCUS + ALOGD("findFocusedWindow finished: injectionResult=%d, " + "timeSpentWaitingForApplication=%0.1fms", + injectionResult, timeSpentWaitingForApplication / 1000000.0); +#endif + return injectionResult; +} + +int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, + const MotionEntry* entry, Vector& inputTargets, nsecs_t* nextWakeupTime, + bool* outConflictingPointerActions) { + enum InjectionPermission { + INJECTION_PERMISSION_UNKNOWN, + INJECTION_PERMISSION_GRANTED, + INJECTION_PERMISSION_DENIED + }; + + 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 displayId = entry->displayId; + 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; + sp newHoverWindowHandle; + + bool isSplit = mTouchState.split; + bool switchedDevice = mTouchState.deviceId >= 0 && mTouchState.displayId >= 0 + && (mTouchState.deviceId != entry->deviceId + || mTouchState.source != entry->source + || mTouchState.displayId != displayId); + bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE + || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER + || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT); + bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN + || maskedAction == AMOTION_EVENT_ACTION_SCROLL + || isHoverAction); + bool wrongDevice = false; + if (newGesture) { + bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; + if (switchedDevice && mTouchState.down && !down) { +#if DEBUG_FOCUS + ALOGD("Dropping event because a pointer for a different device is already down."); +#endif + mTempTouchState.copyFrom(mTouchState); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + switchedDevice = false; + wrongDevice = true; + goto Failed; + } + mTempTouchState.reset(); + mTempTouchState.down = down; + mTempTouchState.deviceId = entry->deviceId; + mTempTouchState.source = entry->source; + mTempTouchState.displayId = displayId; + isSplit = false; + } else { + mTempTouchState.copyFrom(mTouchState); + } + + if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { + /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ + + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + int32_t x = int32_t(entry->pointerCoords[pointerIndex]. + getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(entry->pointerCoords[pointerIndex]. + getAxisValue(AMOTION_EVENT_AXIS_Y)); + sp newTouchedWindowHandle; + sp topErrorWindowHandle; + bool isTouchModal = false; + + // Traverse windows from front to back to find touched window and outside targets. + size_t numWindows = mWindowHandles.size(); + for (size_t i = 0; i < numWindows; i++) { + sp windowHandle = mWindowHandles.itemAt(i); + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + if (windowInfo->displayId != displayId) { + continue; // wrong display + } + + int32_t privateFlags = windowInfo->layoutParamsPrivateFlags; + if (privateFlags & InputWindowInfo::PRIVATE_FLAG_SYSTEM_ERROR) { + if (topErrorWindowHandle == NULL) { + topErrorWindowHandle = windowHandle; + } + } + + int32_t flags = windowInfo->layoutParamsFlags; + if (windowInfo->visible) { + if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) { + isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE + | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0; + if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) { + if (! screenWasOff + || (flags & InputWindowInfo::FLAG_TOUCHABLE_WHEN_WAKING)) { + newTouchedWindowHandle = windowHandle; + } + break; // found touched window, exit window loop + } + } + + if (maskedAction == AMOTION_EVENT_ACTION_DOWN + && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) { + int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE; + if (isWindowObscuredAtPointLocked(windowHandle, x, y)) { + outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } + + mTempTouchState.addOrUpdateWindow( + windowHandle, 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 (topErrorWindowHandle != NULL && newTouchedWindowHandle != topErrorWindowHandle) { + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, NULL, nextWakeupTime, + "Waiting because a system error window is about to be displayed."); + injectionPermission = INJECTION_PERMISSION_UNKNOWN; + goto Unresponsive; + } + + // Figure out whether splitting will be allowed for this window. + if (newTouchedWindowHandle != NULL + && newTouchedWindowHandle->getInfo()->supportsSplitTouch()) { + // New window supports splitting. + isSplit = true; + } else if (isSplit) { + // New window does not support splitting but we have already split events. + // Ignore the new window. + newTouchedWindowHandle = NULL; + } + + // Handle the case where we did not find a window. + if (newTouchedWindowHandle == NULL) { + // Try to assign the pointer to the first foreground window we find, if there is one. + newTouchedWindowHandle = mTempTouchState.getFirstForegroundWindowHandle(); + if (newTouchedWindowHandle == NULL) { + ALOGI("Dropping event because there is no touchable window at (%d, %d).", x, y); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; + } + } + + // Set target flags. + int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS; + if (isSplit) { + targetFlags |= InputTarget::FLAG_SPLIT; + } + if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) { + targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } + + // Update hover state. + if (isHoverAction) { + newHoverWindowHandle = newTouchedWindowHandle; + } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + newHoverWindowHandle = mLastHoverWindowHandle; + } + + // Update the temporary touch state. + BitSet32 pointerIds; + if (isSplit) { + uint32_t pointerId = entry->pointerProperties[pointerIndex].id; + pointerIds.markBit(pointerId); + } + mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, 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_FOCUS + ALOGD("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 whether touches should slip outside of the current foreground window. + if (maskedAction == AMOTION_EVENT_ACTION_MOVE + && entry->pointerCount == 1 + && mTempTouchState.isSlippery()) { + int32_t x = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y)); + + sp oldTouchedWindowHandle = + mTempTouchState.getFirstForegroundWindowHandle(); + sp newTouchedWindowHandle = + findTouchedWindowAtLocked(displayId, x, y); + if (oldTouchedWindowHandle != newTouchedWindowHandle + && newTouchedWindowHandle != NULL) { +#if DEBUG_FOCUS + ALOGD("Touch is slipping out of window %s into window %s.", + oldTouchedWindowHandle->getName().string(), + newTouchedWindowHandle->getName().string()); +#endif + // Make a slippery exit from the old window. + mTempTouchState.addOrUpdateWindow(oldTouchedWindowHandle, + InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT, BitSet32(0)); + + // Make a slippery entrance into the new window. + if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) { + isSplit = true; + } + + int32_t targetFlags = InputTarget::FLAG_FOREGROUND + | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER; + if (isSplit) { + targetFlags |= InputTarget::FLAG_SPLIT; + } + if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) { + targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } + + BitSet32 pointerIds; + if (isSplit) { + pointerIds.markBit(entry->pointerProperties[0].id); + } + mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds); + } + } + } + + if (newHoverWindowHandle != mLastHoverWindowHandle) { + // Let the previous window know that the hover sequence is over. + if (mLastHoverWindowHandle != NULL) { +#if DEBUG_HOVER + ALOGD("Sending hover exit event to window %s.", + mLastHoverWindowHandle->getName().string()); +#endif + mTempTouchState.addOrUpdateWindow(mLastHoverWindowHandle, + InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0)); + } + + // Let the new window know that the hover sequence is starting. + if (newHoverWindowHandle != NULL) { +#if DEBUG_HOVER + ALOGD("Sending hover enter event to window %s.", + newHoverWindowHandle->getName().string()); +#endif + mTempTouchState.addOrUpdateWindow(newHoverWindowHandle, + InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, BitSet32(0)); + } + } + + // 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.windowHandle, + entry->injectionState)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + injectionPermission = INJECTION_PERMISSION_DENIED; + goto Failed; + } + } + } + if (! haveForegroundWindow) { +#if DEBUG_FOCUS + ALOGD("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; + } + + // Check whether windows listening for outside touches are owned by the same UID. If it is + // set the policy flag that we will not reveal coordinate information to this window. + if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + sp foregroundWindowHandle = + mTempTouchState.getFirstForegroundWindowHandle(); + const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid; + for (size_t i = 0; i < mTempTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTempTouchState.windows[i]; + if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { + sp inputWindowHandle = touchedWindow.windowHandle; + if (inputWindowHandle->getInfo()->ownerUid != foregroundWindowUid) { + mTempTouchState.addOrUpdateWindow(inputWindowHandle, + InputTarget::FLAG_ZERO_COORDS, BitSet32(0)); + } + } + } + } + + // 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.windowHandle->getInfo()->paused) { + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.windowHandle, nextWakeupTime, + "Waiting because the touched window is paused."); + goto Unresponsive; + } + + // If the touched window is still working on previous events then keep waiting. + if (!isWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry)) { + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.windowHandle, nextWakeupTime, + "Waiting because the touched window has not finished " + "processing the input events that were previously delivered to it."); + 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. + // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper + // engine only supports touch events. We would need to add a mechanism similar + // to View.onGenericMotionEvent to enable wallpapers to handle these events. + if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + sp foregroundWindowHandle = + mTempTouchState.getFirstForegroundWindowHandle(); + if (foregroundWindowHandle->getInfo()->hasWallpaper) { + for (size_t i = 0; i < mWindowHandles.size(); i++) { + sp windowHandle = mWindowHandles.itemAt(i); + const InputWindowInfo* info = windowHandle->getInfo(); + if (info->displayId == displayId + && windowHandle->getInfo()->layoutParamsType + == InputWindowInfo::TYPE_WALLPAPER) { + mTempTouchState.addOrUpdateWindow(windowHandle, + InputTarget::FLAG_WINDOW_IS_OBSCURED + | InputTarget::FLAG_DISPATCH_AS_IS, + 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.windowHandle, touchedWindow.targetFlags, + touchedWindow.pointerIds, inputTargets); + } + + // Drop the outside or hover touch windows since we will not care about them + // in the next iteration. + mTempTouchState.filterNonAsIsTouchWindows(); + +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 (!wrongDevice) { + if (switchedDevice) { +#if DEBUG_FOCUS + ALOGD("Conflicting pointer actions: Switched to a different device."); +#endif + *outConflictingPointerActions = true; + } + + if (isHoverAction) { + // Started hovering, therefore no longer down. + if (mTouchState.down) { +#if DEBUG_FOCUS + ALOGD("Conflicting pointer actions: Hover received while pointer was down."); +#endif + *outConflictingPointerActions = true; + } + mTouchState.reset(); + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER + || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { + mTouchState.deviceId = entry->deviceId; + mTouchState.source = entry->source; + mTouchState.displayId = displayId; + } + } else if (maskedAction == AMOTION_EVENT_ACTION_UP + || maskedAction == AMOTION_EVENT_ACTION_CANCEL) { + // All pointers up or canceled. + mTouchState.reset(); + } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + // First pointer went down. + if (mTouchState.down) { +#if DEBUG_FOCUS + ALOGD("Conflicting pointer actions: Down received while already down."); +#endif + *outConflictingPointerActions = true; + } + mTouchState.copyFrom(mTempTouchState); + } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + // One pointer went up. + if (isSplit) { + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + uint32_t pointerId = entry->pointerProperties[pointerIndex].id; + + 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; + } + } + mTouchState.copyFrom(mTempTouchState); + } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + // Discard temporary touch state since it was only valid for this action. + } else { + // Save changes to touch state as-is for all other actions. + mTouchState.copyFrom(mTempTouchState); + } + + // Update hover state. + mLastHoverWindowHandle = newHoverWindowHandle; + } + } else { +#if DEBUG_FOCUS + ALOGD("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 + ALOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, " + "timeSpentWaitingForApplication=%0.1fms", + injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0); +#endif + return injectionResult; +} + +void InputDispatcher::addWindowTargetLocked(const sp& windowHandle, + int32_t targetFlags, BitSet32 pointerIds, Vector& inputTargets) { + inputTargets.push(); + + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + InputTarget& target = inputTargets.editTop(); + target.inputChannel = windowInfo->inputChannel; + target.flags = targetFlags; + target.xOffset = - windowInfo->frameLeft; + target.yOffset = - windowInfo->frameTop; + target.scaleFactor = windowInfo->scaleFactor; + target.pointerIds = pointerIds; +} + +void InputDispatcher::addMonitoringTargetsLocked(Vector& inputTargets) { + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + inputTargets.push(); + + InputTarget& target = inputTargets.editTop(); + target.inputChannel = mMonitoringChannels[i]; + target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + target.xOffset = 0; + target.yOffset = 0; + target.pointerIds.clear(); + target.scaleFactor = 1.0f; + } +} + +bool InputDispatcher::checkInjectionPermission(const sp& windowHandle, + const InjectionState* injectionState) { + if (injectionState + && (windowHandle == NULL + || windowHandle->getInfo()->ownerUid != injectionState->injectorUid) + && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) { + if (windowHandle != NULL) { + ALOGW("Permission denied: injecting event from pid %d uid %d to window %s " + "owned by uid %d", + injectionState->injectorPid, injectionState->injectorUid, + windowHandle->getName().string(), + windowHandle->getInfo()->ownerUid); + } else { + ALOGW("Permission denied: injecting event from pid %d uid %d", + injectionState->injectorPid, injectionState->injectorUid); + } + return false; + } + return true; +} + +bool InputDispatcher::isWindowObscuredAtPointLocked( + const sp& windowHandle, int32_t x, int32_t y) const { + int32_t displayId = windowHandle->getInfo()->displayId; + size_t numWindows = mWindowHandles.size(); + for (size_t i = 0; i < numWindows; i++) { + sp otherHandle = mWindowHandles.itemAt(i); + if (otherHandle == windowHandle) { + break; + } + + const InputWindowInfo* otherInfo = otherHandle->getInfo(); + if (otherInfo->displayId == displayId + && otherInfo->visible && !otherInfo->isTrustedOverlay() + && otherInfo->frameContainsPoint(x, y)) { + return true; + } + } + return false; +} + +bool InputDispatcher::isWindowReadyForMoreInputLocked(nsecs_t currentTime, + const sp& windowHandle, const EventEntry* eventEntry) { + ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel()); + if (connectionIndex >= 0) { + sp connection = mConnectionsByFd.valueAt(connectionIndex); + if (connection->inputPublisherBlocked) { + return false; + } + if (eventEntry->type == EventEntry::TYPE_KEY) { + // If the event is a key event, then we must wait for all previous events to + // complete before delivering it because previous events may have the + // side-effect of transferring focus to a different window and we want to + // ensure that the following keys are sent to the new window. + // + // Suppose the user touches a button in a window then immediately presses "A". + // If the button causes a pop-up window to appear then we want to ensure that + // the "A" key is delivered to the new pop-up window. This is because users + // often anticipate pending UI changes when typing on a keyboard. + // To obtain this behavior, we must serialize key events with respect to all + // prior input events. + return connection->outboundQueue.isEmpty() + && connection->waitQueue.isEmpty(); + } + // Touch events can always be sent to a window immediately because the user intended + // to touch whatever was visible at the time. Even if focus changes or a new + // window appears moments later, the touch event was meant to be delivered to + // whatever window happened to be on screen at the time. + // + // Generic motion events, such as trackball or joystick events are a little trickier. + // Like key events, generic motion events are delivered to the focused window. + // Unlike key events, generic motion events don't tend to transfer focus to other + // windows and it is not important for them to be serialized. So we prefer to deliver + // generic motion events as soon as possible to improve efficiency and reduce lag + // through batching. + // + // The one case where we pause input event delivery is when the wait queue is piling + // up with lots of events because the application is not responding. + // This condition ensures that ANRs are detected reliably. + if (!connection->waitQueue.isEmpty() + && currentTime >= connection->waitQueue.head->deliveryTime + + STREAM_AHEAD_EVENT_TIMEOUT) { + return false; + } + } + return true; +} + +String8 InputDispatcher::getApplicationWindowLabelLocked( + const sp& applicationHandle, + const sp& windowHandle) { + if (applicationHandle != NULL) { + if (windowHandle != NULL) { + String8 label(applicationHandle->getName()); + label.append(" - "); + label.append(windowHandle->getName()); + return label; + } else { + return applicationHandle->getName(); + } + } else if (windowHandle != NULL) { + return windowHandle->getName(); + } else { + return String8(""); + } +} + +void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) { + if (mFocusedWindowHandle != NULL) { + const InputWindowInfo* info = mFocusedWindowHandle->getInfo(); + if (info->inputFeatures & InputWindowInfo::INPUT_FEATURE_DISABLE_USER_ACTIVITY) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("Not poking user activity: disabled by window '%s'.", info->name.string()); +#endif + return; + } + } + + int32_t eventType = USER_ACTIVITY_EVENT_OTHER; + switch (eventEntry->type) { + case EventEntry::TYPE_MOTION: { + const MotionEntry* motionEntry = static_cast(eventEntry); + if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) { + return; + } + + if (MotionEvent::isTouchEvent(motionEntry->source, motionEntry->action)) { + eventType = USER_ACTIVITY_EVENT_TOUCH; + } + break; + } + case EventEntry::TYPE_KEY: { + const KeyEntry* keyEntry = static_cast(eventEntry); + if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) { + return; + } + eventType = USER_ACTIVITY_EVENT_BUTTON; + break; + } + } + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doPokeUserActivityLockedInterruptible); + commandEntry->eventTime = eventEntry->eventTime; + commandEntry->userActivityEventType = eventType; +} + +void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, + const sp& connection, EventEntry* eventEntry, const InputTarget* inputTarget) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, " + "xOffset=%f, yOffset=%f, scaleFactor=%f, " + "pointerIds=0x%x", + connection->getInputChannelName(), inputTarget->flags, + inputTarget->xOffset, inputTarget->yOffset, + inputTarget->scaleFactor, inputTarget->pointerIds.value); +#endif + + // 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 + ALOGD("channel '%s' ~ Dropping event because the channel status is %s", + connection->getInputChannelName(), connection->getStatusLabel()); +#endif + return; + } + + // Split a motion event if needed. + if (inputTarget->flags & InputTarget::FLAG_SPLIT) { + ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION); + + MotionEntry* originalMotionEntry = static_cast(eventEntry); + if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) { + MotionEntry* splitMotionEntry = splitMotionEvent( + originalMotionEntry, inputTarget->pointerIds); + if (!splitMotionEntry) { + return; // split event was dropped + } +#if DEBUG_FOCUS + ALOGD("channel '%s' ~ Split motion event.", + connection->getInputChannelName()); + logOutboundMotionDetailsLocked(" ", splitMotionEntry); +#endif + enqueueDispatchEntriesLocked(currentTime, connection, + splitMotionEntry, inputTarget); + splitMotionEntry->release(); + return; + } + } + + // Not splitting. Enqueue dispatch entries for the event as is. + enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget); +} + +void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, + const sp& connection, EventEntry* eventEntry, const InputTarget* inputTarget) { + bool wasEmpty = connection->outboundQueue.isEmpty(); + + // Enqueue dispatch entries for the requested modes. + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + InputTarget::FLAG_DISPATCH_AS_OUTSIDE); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + InputTarget::FLAG_DISPATCH_AS_IS); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER); + + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty && !connection->outboundQueue.isEmpty()) { + startDispatchCycleLocked(currentTime, connection); + } +} + +void InputDispatcher::enqueueDispatchEntryLocked( + const sp& connection, EventEntry* eventEntry, const InputTarget* inputTarget, + int32_t dispatchMode) { + int32_t inputTargetFlags = inputTarget->flags; + if (!(inputTargetFlags & dispatchMode)) { + return; + } + inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode; + + // This is a new event. + // Enqueue a new dispatch entry onto the outbound queue for this connection. + DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref + inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset, + inputTarget->scaleFactor); + + // Apply target flags and update the connection's input state. + switch (eventEntry->type) { + case EventEntry::TYPE_KEY: { + KeyEntry* keyEntry = static_cast(eventEntry); + dispatchEntry->resolvedAction = keyEntry->action; + dispatchEntry->resolvedFlags = keyEntry->flags; + + if (!connection->inputState.trackKey(keyEntry, + dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event", + connection->getInputChannelName()); +#endif + delete dispatchEntry; + return; // skip the inconsistent event + } + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast(eventEntry); + if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { + dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE; + } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) { + dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT; + } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) { + dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER; + } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) { + dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL; + } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) { + dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN; + } else { + dispatchEntry->resolvedAction = motionEntry->action; + } + if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE + && !connection->inputState.isHovering( + motionEntry->deviceId, motionEntry->source, motionEntry->displayId)) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter event", + connection->getInputChannelName()); +#endif + dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER; + } + + dispatchEntry->resolvedFlags = motionEntry->flags; + if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) { + dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + } + + if (!connection->inputState.trackMotion(motionEntry, + dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion event", + connection->getInputChannelName()); +#endif + delete dispatchEntry; + return; // skip the inconsistent event + } + break; + } + } + + // Remember that we are waiting for this dispatch to complete. + if (dispatchEntry->hasForegroundTarget()) { + incrementPendingForegroundDispatchesLocked(eventEntry); + } + + // Enqueue the dispatch entry. + connection->outboundQueue.enqueueAtTail(dispatchEntry); + traceOutboundQueueLengthLocked(connection); +} + +void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, + const sp& connection) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ startDispatchCycle", + connection->getInputChannelName()); +#endif + + while (connection->status == Connection::STATUS_NORMAL + && !connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.head; + dispatchEntry->deliveryTime = currentTime; + + // Publish the event. + status_t status; + EventEntry* eventEntry = dispatchEntry->eventEntry; + switch (eventEntry->type) { + case EventEntry::TYPE_KEY: { + KeyEntry* keyEntry = static_cast(eventEntry); + + // Publish the key event. + status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq, + keyEntry->deviceId, keyEntry->source, + dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags, + keyEntry->keyCode, keyEntry->scanCode, + keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, + keyEntry->eventTime); + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast(eventEntry); + + PointerCoords scaledCoords[MAX_POINTERS]; + const PointerCoords* usingCoords = motionEntry->pointerCoords; + + // Set the X and Y offset depending on the input source. + float xOffset, yOffset, scaleFactor; + if ((motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) + && !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) { + scaleFactor = dispatchEntry->scaleFactor; + xOffset = dispatchEntry->xOffset * scaleFactor; + yOffset = dispatchEntry->yOffset * scaleFactor; + if (scaleFactor != 1.0f) { + for (size_t i = 0; i < motionEntry->pointerCount; i++) { + scaledCoords[i] = motionEntry->pointerCoords[i]; + scaledCoords[i].scale(scaleFactor); + } + usingCoords = scaledCoords; + } + } else { + xOffset = 0.0f; + yOffset = 0.0f; + scaleFactor = 1.0f; + + // We don't want the dispatch target to know. + if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) { + for (size_t i = 0; i < motionEntry->pointerCount; i++) { + scaledCoords[i].clear(); + } + usingCoords = scaledCoords; + } + } + + // Publish the motion event. + status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq, + motionEntry->deviceId, motionEntry->source, + dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags, + motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState, + xOffset, yOffset, + motionEntry->xPrecision, motionEntry->yPrecision, + motionEntry->downTime, motionEntry->eventTime, + motionEntry->pointerCount, motionEntry->pointerProperties, + usingCoords); + break; + } + + default: + ALOG_ASSERT(false); + return; + } + + // Check the result. + if (status) { + if (status == WOULD_BLOCK) { + if (connection->waitQueue.isEmpty()) { + ALOGE("channel '%s' ~ Could not publish event because the pipe is full. " + "This is unexpected because the wait queue is empty, so the pipe " + "should be empty and we shouldn't have any problems writing an " + "event to it, status=%d", connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + } else { + // Pipe is full and we are waiting for the app to finish process some events + // before sending more events to it. +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Could not publish event because the pipe is full, " + "waiting for the application to catch up", + connection->getInputChannelName()); +#endif + connection->inputPublisherBlocked = true; + } + } else { + ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, " + "status=%d", connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + } + return; + } + + // Re-enqueue the event on the wait queue. + connection->outboundQueue.dequeue(dispatchEntry); + traceOutboundQueueLengthLocked(connection); + connection->waitQueue.enqueueAtTail(dispatchEntry); + traceWaitQueueLengthLocked(connection); + } +} + +void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, + const sp& connection, uint32_t seq, bool handled) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s", + connection->getInputChannelName(), seq, toString(handled)); +#endif + + connection->inputPublisherBlocked = false; + + if (connection->status == Connection::STATUS_BROKEN + || connection->status == Connection::STATUS_ZOMBIE) { + return; + } + + // Notify other system components and prepare to start the next dispatch cycle. + onDispatchCycleFinishedLocked(currentTime, connection, seq, handled); +} + +void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime, + const sp& connection, bool notify) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s", + connection->getInputChannelName(), toString(notify)); +#endif + + // Clear the dispatch queues. + drainDispatchQueueLocked(&connection->outboundQueue); + traceOutboundQueueLengthLocked(connection); + drainDispatchQueueLocked(&connection->waitQueue); + traceWaitQueueLengthLocked(connection); + + // The connection appears to be unrecoverably broken. + // Ignore already broken or zombie connections. + if (connection->status == Connection::STATUS_NORMAL) { + connection->status = Connection::STATUS_BROKEN; + + if (notify) { + // Notify other system components. + onDispatchCycleBrokenLocked(currentTime, connection); + } + } +} + +void InputDispatcher::drainDispatchQueueLocked(Queue* queue) { + while (!queue->isEmpty()) { + DispatchEntry* dispatchEntry = queue->dequeueAtHead(); + releaseDispatchEntryLocked(dispatchEntry); + } +} + +void InputDispatcher::releaseDispatchEntryLocked(DispatchEntry* dispatchEntry) { + if (dispatchEntry->hasForegroundTarget()) { + decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry); + } + delete dispatchEntry; +} + +int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) { + InputDispatcher* d = static_cast(data); + + { // acquire lock + AutoMutex _l(d->mLock); + + ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd); + if (connectionIndex < 0) { + ALOGE("Received spurious receive callback for unknown input channel. " + "fd=%d, events=0x%x", fd, events); + return 0; // remove the callback + } + + bool notify; + sp connection = d->mConnectionsByFd.valueAt(connectionIndex); + if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) { + if (!(events & ALOOPER_EVENT_INPUT)) { + ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", connection->getInputChannelName(), events); + return 1; + } + + nsecs_t currentTime = now(); + bool gotOne = false; + status_t status; + for (;;) { + uint32_t seq; + bool handled; + status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled); + if (status) { + break; + } + d->finishDispatchCycleLocked(currentTime, connection, seq, handled); + gotOne = true; + } + if (gotOne) { + d->runCommandsLockedInterruptible(); + if (status == WOULD_BLOCK) { + return 1; + } + } + + notify = status != DEAD_OBJECT || !connection->monitor; + if (notify) { + ALOGE("channel '%s' ~ Failed to receive finished signal. status=%d", + connection->getInputChannelName(), status); + } + } else { + // Monitor channels are never explicitly unregistered. + // We do it automatically when the remote endpoint is closed so don't warn + // about them. + notify = !connection->monitor; + if (notify) { + ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred. " + "events=0x%x", connection->getInputChannelName(), events); + } + } + + // Unregister the channel. + d->unregisterInputChannelLocked(connection->inputChannel, notify); + return 0; // remove the callback + } // release lock +} + +void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked( + const CancelationOptions& options) { + for (size_t i = 0; i < mConnectionsByFd.size(); i++) { + synthesizeCancelationEventsForConnectionLocked( + mConnectionsByFd.valueAt(i), options); + } +} + +void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked( + const sp& channel, const CancelationOptions& options) { + ssize_t index = getConnectionIndexLocked(channel); + if (index >= 0) { + synthesizeCancelationEventsForConnectionLocked( + mConnectionsByFd.valueAt(index), options); + } +} + +void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( + const sp& connection, const CancelationOptions& options) { + if (connection->status == Connection::STATUS_BROKEN) { + return; + } + + nsecs_t currentTime = now(); + + Vector cancelationEvents; + connection->inputState.synthesizeCancelationEvents(currentTime, + cancelationEvents, options); + + if (!cancelationEvents.isEmpty()) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync " + "with reality: %s, mode=%d.", + connection->getInputChannelName(), cancelationEvents.size(), + options.reason, options.mode); +#endif + for (size_t i = 0; i < cancelationEvents.size(); i++) { + EventEntry* cancelationEventEntry = cancelationEvents.itemAt(i); + switch (cancelationEventEntry->type) { + case EventEntry::TYPE_KEY: + logOutboundKeyDetailsLocked("cancel - ", + static_cast(cancelationEventEntry)); + break; + case EventEntry::TYPE_MOTION: + logOutboundMotionDetailsLocked("cancel - ", + static_cast(cancelationEventEntry)); + break; + } + + InputTarget target; + sp windowHandle = getWindowHandleLocked(connection->inputChannel); + if (windowHandle != NULL) { + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + target.xOffset = -windowInfo->frameLeft; + target.yOffset = -windowInfo->frameTop; + target.scaleFactor = windowInfo->scaleFactor; + } else { + target.xOffset = 0; + target.yOffset = 0; + target.scaleFactor = 1.0f; + } + target.inputChannel = connection->inputChannel; + target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + + enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref + &target, InputTarget::FLAG_DISPATCH_AS_IS); + + cancelationEventEntry->release(); + } + + startDispatchCycleLocked(currentTime, connection); + } +} + +InputDispatcher::MotionEntry* +InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) { + ALOG_ASSERT(pointerIds.value != 0); + + uint32_t splitPointerIndexMap[MAX_POINTERS]; + PointerProperties splitPointerProperties[MAX_POINTERS]; + PointerCoords splitPointerCoords[MAX_POINTERS]; + + uint32_t originalPointerCount = originalMotionEntry->pointerCount; + uint32_t splitPointerCount = 0; + + for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount; + originalPointerIndex++) { + const PointerProperties& pointerProperties = + originalMotionEntry->pointerProperties[originalPointerIndex]; + uint32_t pointerId = uint32_t(pointerProperties.id); + if (pointerIds.hasBit(pointerId)) { + splitPointerIndexMap[splitPointerCount] = originalPointerIndex; + splitPointerProperties[splitPointerCount].copyFrom(pointerProperties); + splitPointerCoords[splitPointerCount].copyFrom( + originalMotionEntry->pointerCoords[originalPointerIndex]); + splitPointerCount += 1; + } + } + + if (splitPointerCount != pointerIds.count()) { + // This is bad. We are missing some of the pointers that we expected to deliver. + // Most likely this indicates that we received an ACTION_MOVE events that has + // different pointer ids than we expected based on the previous ACTION_DOWN + // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers + // in this way. + ALOGW("Dropping split motion event because the pointer count is %d but " + "we expected there to be %d pointers. This probably means we received " + "a broken sequence of pointer ids from the input device.", + splitPointerCount, pointerIds.count()); + return NULL; + } + + 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); + const PointerProperties& pointerProperties = + originalMotionEntry->pointerProperties[originalPointerIndex]; + uint32_t pointerId = uint32_t(pointerProperties.id); + 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 != uint32_t(splitPointerProperties[splitPointerIndex].id)) { + splitPointerIndex += 1; + } + action = maskedAction | (splitPointerIndex + << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + } else { + // An unrelated pointer changed. + action = AMOTION_EVENT_ACTION_MOVE; + } + } + + MotionEntry* splitMotionEntry = new MotionEntry( + originalMotionEntry->eventTime, + originalMotionEntry->deviceId, + originalMotionEntry->source, + originalMotionEntry->policyFlags, + action, + originalMotionEntry->flags, + originalMotionEntry->metaState, + originalMotionEntry->buttonState, + originalMotionEntry->edgeFlags, + originalMotionEntry->xPrecision, + originalMotionEntry->yPrecision, + originalMotionEntry->downTime, + originalMotionEntry->displayId, + splitPointerCount, splitPointerProperties, splitPointerCoords); + + if (originalMotionEntry->injectionState) { + splitMotionEntry->injectionState = originalMotionEntry->injectionState; + splitMotionEntry->injectionState->refCount += 1; + } + + return splitMotionEntry; +} + +void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("notifyConfigurationChanged - eventTime=%lld", args->eventTime); +#endif + + bool needWake; + { // acquire lock + AutoMutex _l(mLock); + + ConfigurationChangedEntry* newEntry = new ConfigurationChangedEntry(args->eventTime); + needWake = enqueueInboundEventLocked(newEntry); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("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", + args->eventTime, args->deviceId, args->source, args->policyFlags, + args->action, args->flags, args->keyCode, args->scanCode, + args->metaState, args->downTime); +#endif + if (!validateKeyEvent(args->action)) { + return; + } + + uint32_t policyFlags = args->policyFlags; + int32_t flags = args->flags; + int32_t metaState = args->metaState; + if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) { + policyFlags |= POLICY_FLAG_VIRTUAL; + flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY; + } + if (policyFlags & POLICY_FLAG_ALT) { + metaState |= AMETA_ALT_ON | AMETA_ALT_LEFT_ON; + } + if (policyFlags & POLICY_FLAG_ALT_GR) { + metaState |= AMETA_ALT_ON | AMETA_ALT_RIGHT_ON; + } + if (policyFlags & POLICY_FLAG_SHIFT) { + metaState |= AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON; + } + if (policyFlags & POLICY_FLAG_CAPS_LOCK) { + metaState |= AMETA_CAPS_LOCK_ON; + } + if (policyFlags & POLICY_FLAG_FUNCTION) { + metaState |= AMETA_FUNCTION_ON; + } + + policyFlags |= POLICY_FLAG_TRUSTED; + + KeyEvent event; + event.initialize(args->deviceId, args->source, args->action, + flags, args->keyCode, args->scanCode, metaState, 0, + args->downTime, args->eventTime); + + mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); + + if (policyFlags & POLICY_FLAG_WOKE_HERE) { + flags |= AKEY_EVENT_FLAG_WOKE_HERE; + } + + bool needWake; + { // acquire lock + mLock.lock(); + + if (shouldSendKeyToInputFilterLocked(args)) { + mLock.unlock(); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } + + int32_t repeatCount = 0; + KeyEntry* newEntry = new KeyEntry(args->eventTime, + args->deviceId, args->source, policyFlags, + args->action, flags, args->keyCode, args->scanCode, + metaState, repeatCount, args->downTime); + + needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) { + return mInputFilterEnabled; +} + +void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("notifyMotion - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, " + "action=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, edgeFlags=0x%x, " + "xPrecision=%f, yPrecision=%f, downTime=%lld", + args->eventTime, args->deviceId, args->source, args->policyFlags, + args->action, args->flags, args->metaState, args->buttonState, + args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime); + for (uint32_t i = 0; i < args->pointerCount; i++) { + ALOGD(" Pointer %d: id=%d, toolType=%d, " + "x=%f, y=%f, pressure=%f, size=%f, " + "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " + "orientation=%f", + i, args->pointerProperties[i].id, + args->pointerProperties[i].toolType, + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), + args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); + } +#endif + if (!validateMotionEvent(args->action, args->pointerCount, args->pointerProperties)) { + return; + } + + uint32_t policyFlags = args->policyFlags; + policyFlags |= POLICY_FLAG_TRUSTED; + mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags); + + bool needWake; + { // acquire lock + mLock.lock(); + + if (shouldSendMotionToInputFilterLocked(args)) { + mLock.unlock(); + + MotionEvent event; + event.initialize(args->deviceId, args->source, args->action, args->flags, + args->edgeFlags, args->metaState, args->buttonState, 0, 0, + args->xPrecision, args->yPrecision, + args->downTime, args->eventTime, + args->pointerCount, args->pointerProperties, args->pointerCoords); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } + + // Just enqueue a new motion event. + MotionEntry* newEntry = new MotionEntry(args->eventTime, + args->deviceId, args->source, policyFlags, + args->action, args->flags, args->metaState, args->buttonState, + args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime, + args->displayId, + args->pointerCount, args->pointerProperties, args->pointerCoords); + + needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) { + // TODO: support sending secondary display events to input filter + return mInputFilterEnabled && isMainDisplay(args->displayId); +} + +void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) { +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("notifySwitch - eventTime=%lld, policyFlags=0x%x, switchValues=0x%08x, switchMask=0x%08x", + args->eventTime, args->policyFlags, + args->switchValues, args->switchMask); +#endif + + uint32_t policyFlags = args->policyFlags; + policyFlags |= POLICY_FLAG_TRUSTED; + mPolicy->notifySwitch(args->eventTime, + args->switchValues, args->switchMask, policyFlags); +} + +void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) { +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("notifyDeviceReset - eventTime=%lld, deviceId=%d", + args->eventTime, args->deviceId); +#endif + + bool needWake; + { // acquire lock + AutoMutex _l(mLock); + + DeviceResetEntry* newEntry = new DeviceResetEntry(args->eventTime, args->deviceId); + needWake = enqueueInboundEventLocked(newEntry); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +int32_t InputDispatcher::injectInputEvent(const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) { +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " + "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x", + event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags); +#endif + + nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); + + policyFlags |= POLICY_FLAG_INJECTED; + if (hasInjectionPermission(injectorPid, injectorUid)) { + policyFlags |= POLICY_FLAG_TRUSTED; + } + + EventEntry* firstInjectedEntry; + EventEntry* lastInjectedEntry; + switch (event->getType()) { + case AINPUT_EVENT_TYPE_KEY: { + const KeyEvent* keyEvent = static_cast(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; + } + + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + } + + if (policyFlags & POLICY_FLAG_WOKE_HERE) { + flags |= AKEY_EVENT_FLAG_WOKE_HERE; + } + + mLock.lock(); + firstInjectedEntry = new KeyEntry(keyEvent->getEventTime(), + keyEvent->getDeviceId(), keyEvent->getSource(), + policyFlags, action, flags, + keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(), + keyEvent->getRepeatCount(), keyEvent->getDownTime()); + lastInjectedEntry = firstInjectedEntry; + break; + } + + case AINPUT_EVENT_TYPE_MOTION: { + const MotionEvent* motionEvent = static_cast(event); + int32_t displayId = ADISPLAY_ID_DEFAULT; + int32_t action = motionEvent->getAction(); + size_t pointerCount = motionEvent->getPointerCount(); + const PointerProperties* pointerProperties = motionEvent->getPointerProperties(); + if (! validateMotionEvent(action, pointerCount, pointerProperties)) { + return INPUT_EVENT_INJECTION_FAILED; + } + + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + nsecs_t eventTime = motionEvent->getEventTime(); + mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + } + + mLock.lock(); + const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes(); + const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords(); + firstInjectedEntry = new MotionEntry(*sampleEventTimes, + motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags, + action, motionEvent->getFlags(), + motionEvent->getMetaState(), motionEvent->getButtonState(), + motionEvent->getEdgeFlags(), + motionEvent->getXPrecision(), motionEvent->getYPrecision(), + motionEvent->getDownTime(), displayId, + uint32_t(pointerCount), pointerProperties, samplePointerCoords); + lastInjectedEntry = firstInjectedEntry; + for (size_t i = motionEvent->getHistorySize(); i > 0; i--) { + sampleEventTimes += 1; + samplePointerCoords += pointerCount; + MotionEntry* nextInjectedEntry = new MotionEntry(*sampleEventTimes, + motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags, + action, motionEvent->getFlags(), + motionEvent->getMetaState(), motionEvent->getButtonState(), + motionEvent->getEdgeFlags(), + motionEvent->getXPrecision(), motionEvent->getYPrecision(), + motionEvent->getDownTime(), displayId, + uint32_t(pointerCount), pointerProperties, samplePointerCoords); + lastInjectedEntry->next = nextInjectedEntry; + lastInjectedEntry = nextInjectedEntry; + } + break; + } + + default: + ALOGW("Cannot inject event of type %d", event->getType()); + return INPUT_EVENT_INJECTION_FAILED; + } + + InjectionState* injectionState = new InjectionState(injectorPid, injectorUid); + if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { + injectionState->injectionIsAsync = true; + } + + injectionState->refCount += 1; + lastInjectedEntry->injectionState = injectionState; + + bool needWake = false; + for (EventEntry* entry = firstInjectedEntry; entry != NULL; ) { + EventEntry* nextEntry = entry->next; + needWake |= enqueueInboundEventLocked(entry); + entry = nextEntry; + } + + 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 + ALOGD("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 + ALOGD("injectInputEvent - Waiting for %d pending foreground dispatches.", + injectionState->pendingForegroundDispatches); +#endif + nsecs_t remainingTimeout = endTime - now(); + if (remainingTimeout <= 0) { +#if DEBUG_INJECTION + ALOGD("injectInputEvent - Timed out waiting for pending foreground " + "dispatches to finish."); +#endif + injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; + break; + } + + mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout); + } + } + } + + injectionState->release(); + } // release lock + +#if DEBUG_INJECTION + ALOGD("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 + ALOGD("Setting input event injection result to %d. " + "injectorPid=%d, injectorUid=%d", + injectionResult, injectionState->injectorPid, injectionState->injectorUid); +#endif + + if (injectionState->injectionIsAsync + && !(entry->policyFlags & POLICY_FLAG_FILTERED)) { + // Log the outcome since the injector did not wait for the injection result. + switch (injectionResult) { + case INPUT_EVENT_INJECTION_SUCCEEDED: + ALOGV("Asynchronous input event injection succeeded."); + break; + case INPUT_EVENT_INJECTION_FAILED: + ALOGW("Asynchronous input event injection failed."); + break; + case INPUT_EVENT_INJECTION_PERMISSION_DENIED: + ALOGW("Asynchronous input event injection permission denied."); + break; + case INPUT_EVENT_INJECTION_TIMED_OUT: + ALOGW("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(); + } + } +} + +sp InputDispatcher::getWindowHandleLocked( + const sp& inputChannel) const { + size_t numWindows = mWindowHandles.size(); + for (size_t i = 0; i < numWindows; i++) { + const sp& windowHandle = mWindowHandles.itemAt(i); + if (windowHandle->getInputChannel() == inputChannel) { + return windowHandle; + } + } + return NULL; +} + +bool InputDispatcher::hasWindowHandleLocked( + const sp& windowHandle) const { + size_t numWindows = mWindowHandles.size(); + for (size_t i = 0; i < numWindows; i++) { + if (mWindowHandles.itemAt(i) == windowHandle) { + return true; + } + } + return false; +} + +void InputDispatcher::setInputWindows(const Vector >& inputWindowHandles) { +#if DEBUG_FOCUS + ALOGD("setInputWindows"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + Vector > oldWindowHandles = mWindowHandles; + mWindowHandles = inputWindowHandles; + + sp newFocusedWindowHandle; + bool foundHoveredWindow = false; + for (size_t i = 0; i < mWindowHandles.size(); i++) { + const sp& windowHandle = mWindowHandles.itemAt(i); + if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) { + mWindowHandles.removeAt(i--); + continue; + } + if (windowHandle->getInfo()->hasFocus) { + newFocusedWindowHandle = windowHandle; + } + if (windowHandle == mLastHoverWindowHandle) { + foundHoveredWindow = true; + } + } + + if (!foundHoveredWindow) { + mLastHoverWindowHandle = NULL; + } + + if (mFocusedWindowHandle != newFocusedWindowHandle) { + if (mFocusedWindowHandle != NULL) { +#if DEBUG_FOCUS + ALOGD("Focus left window: %s", + mFocusedWindowHandle->getName().string()); +#endif + sp focusedInputChannel = mFocusedWindowHandle->getInputChannel(); + if (focusedInputChannel != NULL) { + CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, + "focus left window"); + synthesizeCancelationEventsForInputChannelLocked( + focusedInputChannel, options); + } + } + if (newFocusedWindowHandle != NULL) { +#if DEBUG_FOCUS + ALOGD("Focus entered window: %s", + newFocusedWindowHandle->getName().string()); +#endif + } + mFocusedWindowHandle = newFocusedWindowHandle; + } + + for (size_t i = 0; i < mTouchState.windows.size(); i++) { + TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i); + if (!hasWindowHandleLocked(touchedWindow.windowHandle)) { +#if DEBUG_FOCUS + ALOGD("Touched window was removed: %s", + touchedWindow.windowHandle->getName().string()); +#endif + sp touchedInputChannel = + touchedWindow.windowHandle->getInputChannel(); + if (touchedInputChannel != NULL) { + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + "touched window was removed"); + synthesizeCancelationEventsForInputChannelLocked( + touchedInputChannel, options); + } + mTouchState.windows.removeAt(i--); + } + } + + // Release information for windows that are no longer present. + // This ensures that unused input channels are released promptly. + // Otherwise, they might stick around until the window handle is destroyed + // which might not happen until the next GC. + for (size_t i = 0; i < oldWindowHandles.size(); i++) { + const sp& oldWindowHandle = oldWindowHandles.itemAt(i); + if (!hasWindowHandleLocked(oldWindowHandle)) { +#if DEBUG_FOCUS + ALOGD("Window went away: %s", oldWindowHandle->getName().string()); +#endif + oldWindowHandle->releaseInfo(); + } + } + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mLooper->wake(); +} + +void InputDispatcher::setFocusedApplication( + const sp& inputApplicationHandle) { +#if DEBUG_FOCUS + ALOGD("setFocusedApplication"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + if (inputApplicationHandle != NULL && inputApplicationHandle->updateInfo()) { + if (mFocusedApplicationHandle != inputApplicationHandle) { + if (mFocusedApplicationHandle != NULL) { + resetANRTimeoutsLocked(); + mFocusedApplicationHandle->releaseInfo(); + } + mFocusedApplicationHandle = inputApplicationHandle; + } + } else if (mFocusedApplicationHandle != NULL) { + resetANRTimeoutsLocked(); + mFocusedApplicationHandle->releaseInfo(); + mFocusedApplicationHandle.clear(); + } + +#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::setInputDispatchMode(bool enabled, bool frozen) { +#if DEBUG_FOCUS + ALOGD("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(); + } +} + +void InputDispatcher::setInputFilterEnabled(bool enabled) { +#if DEBUG_FOCUS + ALOGD("setInputFilterEnabled: enabled=%d", enabled); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + if (mInputFilterEnabled == enabled) { + return; + } + + mInputFilterEnabled = enabled; + resetAndDropEverythingLocked("input filter is being enabled or disabled"); + } // release lock + + // Wake up poll loop since there might be work to do to drop everything. + mLooper->wake(); +} + +bool InputDispatcher::transferTouchFocus(const sp& fromChannel, + const sp& toChannel) { +#if DEBUG_FOCUS + ALOGD("transferTouchFocus: fromChannel=%s, toChannel=%s", + fromChannel->getName().string(), toChannel->getName().string()); +#endif + { // acquire lock + AutoMutex _l(mLock); + + sp fromWindowHandle = getWindowHandleLocked(fromChannel); + sp toWindowHandle = getWindowHandleLocked(toChannel); + if (fromWindowHandle == NULL || toWindowHandle == NULL) { +#if DEBUG_FOCUS + ALOGD("Cannot transfer focus because from or to window not found."); +#endif + return false; + } + if (fromWindowHandle == toWindowHandle) { +#if DEBUG_FOCUS + ALOGD("Trivial transfer to same window."); +#endif + return true; + } + if (fromWindowHandle->getInfo()->displayId != toWindowHandle->getInfo()->displayId) { +#if DEBUG_FOCUS + ALOGD("Cannot transfer focus because windows are on different displays."); +#endif + return false; + } + + bool found = false; + for (size_t i = 0; i < mTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTouchState.windows[i]; + if (touchedWindow.windowHandle == fromWindowHandle) { + int32_t oldTargetFlags = touchedWindow.targetFlags; + BitSet32 pointerIds = touchedWindow.pointerIds; + + mTouchState.windows.removeAt(i); + + int32_t newTargetFlags = oldTargetFlags + & (InputTarget::FLAG_FOREGROUND + | InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS); + mTouchState.addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds); + + found = true; + break; + } + } + + if (! found) { +#if DEBUG_FOCUS + ALOGD("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 fromConnection = mConnectionsByFd.valueAt(fromConnectionIndex); + sp toConnection = mConnectionsByFd.valueAt(toConnectionIndex); + + fromConnection->inputState.copyPointerStateTo(toConnection->inputState); + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + "transferring touch focus from this window to another window"); + synthesizeCancelationEventsForConnectionLocked(fromConnection, options); + } + +#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 + ALOGD("Resetting and dropping all events (%s).", reason); +#endif + + CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); + + resetKeyRepeatLocked(); + releasePendingEventLocked(); + drainInboundQueueLocked(); + resetANRTimeoutsLocked(); + + mTouchState.reset(); + mLastHoverWindowHandle.clear(); +} + +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'; + } + ALOGD("%s", start); + start = end; + } +} + +void InputDispatcher::dumpDispatchStateLocked(String8& dump) { + dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled); + dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen); + + if (mFocusedApplicationHandle != NULL) { + dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n", + mFocusedApplicationHandle->getName().string(), + mFocusedApplicationHandle->getDispatchingTimeout( + DEFAULT_INPUT_DISPATCHING_TIMEOUT) / 1000000.0); + } else { + dump.append(INDENT "FocusedApplication: \n"); + } + dump.appendFormat(INDENT "FocusedWindow: name='%s'\n", + mFocusedWindowHandle != NULL ? mFocusedWindowHandle->getName().string() : ""); + + dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down)); + dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split)); + dump.appendFormat(INDENT "TouchDeviceId: %d\n", mTouchState.deviceId); + dump.appendFormat(INDENT "TouchSource: 0x%08x\n", mTouchState.source); + dump.appendFormat(INDENT "TouchDisplayId: %d\n", mTouchState.displayId); + 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.windowHandle->getName().string(), + touchedWindow.pointerIds.value, + touchedWindow.targetFlags); + } + } else { + dump.append(INDENT "TouchedWindows: \n"); + } + + if (!mWindowHandles.isEmpty()) { + dump.append(INDENT "Windows:\n"); + for (size_t i = 0; i < mWindowHandles.size(); i++) { + const sp& windowHandle = mWindowHandles.itemAt(i); + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + + dump.appendFormat(INDENT2 "%d: name='%s', displayId=%d, " + "paused=%s, hasFocus=%s, hasWallpaper=%s, " + "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, " + "frame=[%d,%d][%d,%d], scale=%f, " + "touchableRegion=", + i, windowInfo->name.string(), windowInfo->displayId, + toString(windowInfo->paused), + toString(windowInfo->hasFocus), + toString(windowInfo->hasWallpaper), + toString(windowInfo->visible), + toString(windowInfo->canReceiveKeys), + windowInfo->layoutParamsFlags, windowInfo->layoutParamsType, + windowInfo->layer, + windowInfo->frameLeft, windowInfo->frameTop, + windowInfo->frameRight, windowInfo->frameBottom, + windowInfo->scaleFactor); + dumpRegion(dump, windowInfo->touchableRegion); + dump.appendFormat(", inputFeatures=0x%08x", windowInfo->inputFeatures); + dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n", + windowInfo->ownerPid, windowInfo->ownerUid, + windowInfo->dispatchingTimeout / 1000000.0); + } + } else { + dump.append(INDENT "Windows: \n"); + } + + if (!mMonitoringChannels.isEmpty()) { + dump.append(INDENT "MonitoringChannels:\n"); + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + const sp& channel = mMonitoringChannels[i]; + dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string()); + } + } else { + dump.append(INDENT "MonitoringChannels: \n"); + } + + nsecs_t currentTime = now(); + + // Dump recently dispatched or dropped events from oldest to newest. + if (!mRecentQueue.isEmpty()) { + dump.appendFormat(INDENT "RecentQueue: length=%u\n", mRecentQueue.count()); + for (EventEntry* entry = mRecentQueue.head; entry; entry = entry->next) { + dump.append(INDENT2); + entry->appendDescription(dump); + dump.appendFormat(", age=%0.1fms\n", + (currentTime - entry->eventTime) * 0.000001f); + } + } else { + dump.append(INDENT "RecentQueue: \n"); + } + + // Dump event currently being dispatched. + if (mPendingEvent) { + dump.append(INDENT "PendingEvent:\n"); + dump.append(INDENT2); + mPendingEvent->appendDescription(dump); + dump.appendFormat(", age=%0.1fms\n", + (currentTime - mPendingEvent->eventTime) * 0.000001f); + } else { + dump.append(INDENT "PendingEvent: \n"); + } + + // Dump inbound events from oldest to newest. + if (!mInboundQueue.isEmpty()) { + dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count()); + for (EventEntry* entry = mInboundQueue.head; entry; entry = entry->next) { + dump.append(INDENT2); + entry->appendDescription(dump); + dump.appendFormat(", age=%0.1fms\n", + (currentTime - entry->eventTime) * 0.000001f); + } + } else { + dump.append(INDENT "InboundQueue: \n"); + } + + if (!mConnectionsByFd.isEmpty()) { + dump.append(INDENT "Connections:\n"); + for (size_t i = 0; i < mConnectionsByFd.size(); i++) { + const sp& connection = mConnectionsByFd.valueAt(i); + dump.appendFormat(INDENT2 "%d: channelName='%s', windowName='%s', " + "status=%s, monitor=%s, inputPublisherBlocked=%s\n", + i, connection->getInputChannelName(), connection->getWindowName(), + connection->getStatusLabel(), toString(connection->monitor), + toString(connection->inputPublisherBlocked)); + + if (!connection->outboundQueue.isEmpty()) { + dump.appendFormat(INDENT3 "OutboundQueue: length=%u\n", + connection->outboundQueue.count()); + for (DispatchEntry* entry = connection->outboundQueue.head; entry; + entry = entry->next) { + dump.append(INDENT4); + entry->eventEntry->appendDescription(dump); + dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, age=%0.1fms\n", + entry->targetFlags, entry->resolvedAction, + (currentTime - entry->eventEntry->eventTime) * 0.000001f); + } + } else { + dump.append(INDENT3 "OutboundQueue: \n"); + } + + if (!connection->waitQueue.isEmpty()) { + dump.appendFormat(INDENT3 "WaitQueue: length=%u\n", + connection->waitQueue.count()); + for (DispatchEntry* entry = connection->waitQueue.head; entry; + entry = entry->next) { + dump.append(INDENT4); + entry->eventEntry->appendDescription(dump); + dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, " + "age=%0.1fms, wait=%0.1fms\n", + entry->targetFlags, entry->resolvedAction, + (currentTime - entry->eventEntry->eventTime) * 0.000001f, + (currentTime - entry->deliveryTime) * 0.000001f); + } + } else { + dump.append(INDENT3 "WaitQueue: \n"); + } + } + } else { + dump.append(INDENT "Connections: \n"); + } + + if (isAppSwitchPendingLocked()) { + dump.appendFormat(INDENT "AppSwitch: pending, due in %0.1fms\n", + (mAppSwitchDueTime - now()) / 1000000.0); + } else { + dump.append(INDENT "AppSwitch: not pending\n"); + } + + dump.append(INDENT "Configuration:\n"); + dump.appendFormat(INDENT2 "KeyRepeatDelay: %0.1fms\n", + mConfig.keyRepeatDelay * 0.000001f); + dump.appendFormat(INDENT2 "KeyRepeatTimeout: %0.1fms\n", + mConfig.keyRepeatTimeout * 0.000001f); +} + +status_t InputDispatcher::registerInputChannel(const sp& inputChannel, + const sp& inputWindowHandle, bool monitor) { +#if DEBUG_REGISTRATION + ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(), + toString(monitor)); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + if (getConnectionIndexLocked(inputChannel) >= 0) { + ALOGW("Attempted to register already registered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp connection = new Connection(inputChannel, inputWindowHandle, monitor); + + int fd = inputChannel->getFd(); + mConnectionsByFd.add(fd, connection); + + if (monitor) { + mMonitoringChannels.push(inputChannel); + } + + mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); + } // release lock + + // Wake the looper because some connections have changed. + mLooper->wake(); + return OK; +} + +status_t InputDispatcher::unregisterInputChannel(const sp& inputChannel) { +#if DEBUG_REGISTRATION + ALOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string()); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + status_t status = unregisterInputChannelLocked(inputChannel, false /*notify*/); + if (status) { + return status; + } + } // release lock + + // Wake the poll loop because removing the connection may have changed the current + // synchronization state. + mLooper->wake(); + return OK; +} + +status_t InputDispatcher::unregisterInputChannelLocked(const sp& inputChannel, + bool notify) { + ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); + if (connectionIndex < 0) { + ALOGW("Attempted to unregister already unregistered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp connection = mConnectionsByFd.valueAt(connectionIndex); + mConnectionsByFd.removeItemsAt(connectionIndex); + + if (connection->monitor) { + removeMonitorChannelLocked(inputChannel); + } + + mLooper->removeFd(inputChannel->getFd()); + + nsecs_t currentTime = now(); + abortBrokenDispatchCycleLocked(currentTime, connection, notify); + + connection->status = Connection::STATUS_ZOMBIE; + return OK; +} + +void InputDispatcher::removeMonitorChannelLocked(const sp& inputChannel) { + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + if (mMonitoringChannels[i] == inputChannel) { + mMonitoringChannels.removeAt(i); + break; + } + } +} + +ssize_t InputDispatcher::getConnectionIndexLocked(const sp& inputChannel) { + ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd()); + if (connectionIndex >= 0) { + sp connection = mConnectionsByFd.valueAt(connectionIndex); + if (connection->inputChannel.get() == inputChannel.get()) { + return connectionIndex; + } + } + + return -1; +} + +void InputDispatcher::onDispatchCycleFinishedLocked( + nsecs_t currentTime, const sp& connection, uint32_t seq, bool handled) { + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doDispatchCycleFinishedLockedInterruptible); + commandEntry->connection = connection; + commandEntry->eventTime = currentTime; + commandEntry->seq = seq; + commandEntry->handled = handled; +} + +void InputDispatcher::onDispatchCycleBrokenLocked( + nsecs_t currentTime, const sp& connection) { + ALOGE("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 sp& applicationHandle, + const sp& windowHandle, + nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) { + float dispatchLatency = (currentTime - eventTime) * 0.000001f; + float waitDuration = (currentTime - waitStartTime) * 0.000001f; + ALOGI("Application is not responding: %s. " + "It has been %0.1fms since event, %0.1fms since wait started. Reason: %s", + getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(), + dispatchLatency, waitDuration, reason); + + // Capture a record of the InputDispatcher state at the time of the ANR. + time_t t = time(NULL); + struct tm tm; + localtime_r(&t, &tm); + char timestr[64]; + strftime(timestr, sizeof(timestr), "%F %T", &tm); + mLastANRState.clear(); + mLastANRState.append(INDENT "ANR:\n"); + mLastANRState.appendFormat(INDENT2 "Time: %s\n", timestr); + mLastANRState.appendFormat(INDENT2 "Window: %s\n", + getApplicationWindowLabelLocked(applicationHandle, windowHandle).string()); + mLastANRState.appendFormat(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency); + mLastANRState.appendFormat(INDENT2 "WaitDuration: %0.1fms\n", waitDuration); + mLastANRState.appendFormat(INDENT2 "Reason: %s\n", reason); + dumpDispatchStateLocked(mLastANRState); + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyANRLockedInterruptible); + commandEntry->inputApplicationHandle = applicationHandle; + commandEntry->inputWindowHandle = windowHandle; + commandEntry->reason = reason; +} + +void InputDispatcher::doNotifyConfigurationChangedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->notifyConfigurationChanged(commandEntry->eventTime); + + mLock.lock(); +} + +void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible( + CommandEntry* commandEntry) { + sp connection = commandEntry->connection; + + if (connection->status != Connection::STATUS_ZOMBIE) { + mLock.unlock(); + + mPolicy->notifyInputChannelBroken(connection->inputWindowHandle); + + mLock.lock(); + } +} + +void InputDispatcher::doNotifyANRLockedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + nsecs_t newTimeout = mPolicy->notifyANR( + commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle, + commandEntry->reason); + + mLock.lock(); + + resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, + commandEntry->inputWindowHandle != NULL + ? commandEntry->inputWindowHandle->getInputChannel() : NULL); +} + +void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( + CommandEntry* commandEntry) { + KeyEntry* entry = commandEntry->keyEntry; + + KeyEvent event; + initializeKeyEvent(&event, entry); + + mLock.unlock(); + + nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle, + &event, entry->policyFlags); + + mLock.lock(); + + if (delay < 0) { + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP; + } else if (!delay) { + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + } else { + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER; + entry->interceptKeyWakeupTime = now() + delay; + } + entry->release(); +} + +void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( + CommandEntry* commandEntry) { + sp connection = commandEntry->connection; + nsecs_t finishTime = commandEntry->eventTime; + uint32_t seq = commandEntry->seq; + bool handled = commandEntry->handled; + + // Handle post-event policy actions. + DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq); + if (dispatchEntry) { + nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime; + if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) { + String8 msg; + msg.appendFormat("Window '%s' spent %0.1fms processing the last input event: ", + connection->getWindowName(), eventDuration * 0.000001f); + dispatchEntry->eventEntry->appendDescription(msg); + ALOGI("%s", msg.string()); + } + + bool restartEvent; + if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) { + KeyEntry* keyEntry = static_cast(dispatchEntry->eventEntry); + restartEvent = afterKeyEventLockedInterruptible(connection, + dispatchEntry, keyEntry, handled); + } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) { + MotionEntry* motionEntry = static_cast(dispatchEntry->eventEntry); + restartEvent = afterMotionEventLockedInterruptible(connection, + dispatchEntry, motionEntry, handled); + } else { + restartEvent = false; + } + + // Dequeue the event and start the next cycle. + // Note that because the lock might have been released, it is possible that the + // contents of the wait queue to have been drained, so we need to double-check + // a few things. + if (dispatchEntry == connection->findWaitQueueEntry(seq)) { + connection->waitQueue.dequeue(dispatchEntry); + traceWaitQueueLengthLocked(connection); + if (restartEvent && connection->status == Connection::STATUS_NORMAL) { + connection->outboundQueue.enqueueAtHead(dispatchEntry); + traceOutboundQueueLengthLocked(connection); + } else { + releaseDispatchEntryLocked(dispatchEntry); + } + } + + // Start the next dispatch cycle for this connection. + startDispatchCycleLocked(now(), connection); + } +} + +bool InputDispatcher::afterKeyEventLockedInterruptible(const sp& connection, + DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled) { + if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) { + // Get the fallback key state. + // Clear it out after dispatching the UP. + int32_t originalKeyCode = keyEntry->keyCode; + int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode); + if (keyEntry->action == AKEY_EVENT_ACTION_UP) { + connection->inputState.removeFallbackKey(originalKeyCode); + } + + if (handled || !dispatchEntry->hasForegroundTarget()) { + // If the application handles the original key for which we previously + // generated a fallback or if the window is not a foreground window, + // then cancel the associated fallback key, if any. + if (fallbackKeyCode != -1) { + // Dispatch the unhandled key to the policy with the cancel flag. +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Unhandled key event: Asking policy to cancel fallback action. " + "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x", + keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount, + keyEntry->policyFlags); +#endif + KeyEvent event; + initializeKeyEvent(&event, keyEntry); + event.setFlags(event.getFlags() | AKEY_EVENT_FLAG_CANCELED); + + mLock.unlock(); + + mPolicy->dispatchUnhandledKey(connection->inputWindowHandle, + &event, keyEntry->policyFlags, &event); + + mLock.lock(); + + // Cancel the fallback key. + if (fallbackKeyCode != AKEYCODE_UNKNOWN) { + CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS, + "application handled the original non-fallback key " + "or is no longer a foreground target, " + "canceling previously dispatched fallback key"); + options.keyCode = fallbackKeyCode; + synthesizeCancelationEventsForConnectionLocked(connection, options); + } + connection->inputState.removeFallbackKey(originalKeyCode); + } + } else { + // If the application did not handle a non-fallback key, first check + // that we are in a good state to perform unhandled key event processing + // Then ask the policy what to do with it. + bool initialDown = keyEntry->action == AKEY_EVENT_ACTION_DOWN + && keyEntry->repeatCount == 0; + if (fallbackKeyCode == -1 && !initialDown) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Unhandled key event: Skipping unhandled key event processing " + "since this is not an initial down. " + "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x", + originalKeyCode, keyEntry->action, keyEntry->repeatCount, + keyEntry->policyFlags); +#endif + return false; + } + + // Dispatch the unhandled key to the policy. +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Unhandled key event: Asking policy to perform fallback action. " + "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x", + keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount, + keyEntry->policyFlags); +#endif + KeyEvent event; + initializeKeyEvent(&event, keyEntry); + + mLock.unlock(); + + bool fallback = mPolicy->dispatchUnhandledKey(connection->inputWindowHandle, + &event, keyEntry->policyFlags, &event); + + mLock.lock(); + + if (connection->status != Connection::STATUS_NORMAL) { + connection->inputState.removeFallbackKey(originalKeyCode); + return false; + } + + // Latch the fallback keycode for this key on an initial down. + // The fallback keycode cannot change at any other point in the lifecycle. + if (initialDown) { + if (fallback) { + fallbackKeyCode = event.getKeyCode(); + } else { + fallbackKeyCode = AKEYCODE_UNKNOWN; + } + connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode); + } + + ALOG_ASSERT(fallbackKeyCode != -1); + + // Cancel the fallback key if the policy decides not to send it anymore. + // We will continue to dispatch the key to the policy but we will no + // longer dispatch a fallback key to the application. + if (fallbackKeyCode != AKEYCODE_UNKNOWN + && (!fallback || fallbackKeyCode != event.getKeyCode())) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + if (fallback) { + ALOGD("Unhandled key event: Policy requested to send key %d" + "as a fallback for %d, but on the DOWN it had requested " + "to send %d instead. Fallback canceled.", + event.getKeyCode(), originalKeyCode, fallbackKeyCode); + } else { + ALOGD("Unhandled key event: Policy did not request fallback for %d, " + "but on the DOWN it had requested to send %d. " + "Fallback canceled.", + originalKeyCode, fallbackKeyCode); + } +#endif + + CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS, + "canceling fallback, policy no longer desires it"); + options.keyCode = fallbackKeyCode; + synthesizeCancelationEventsForConnectionLocked(connection, options); + + fallback = false; + fallbackKeyCode = AKEYCODE_UNKNOWN; + if (keyEntry->action != AKEY_EVENT_ACTION_UP) { + connection->inputState.setFallbackKey(originalKeyCode, + fallbackKeyCode); + } + } + +#if DEBUG_OUTBOUND_EVENT_DETAILS + { + String8 msg; + const KeyedVector& fallbackKeys = + connection->inputState.getFallbackKeys(); + for (size_t i = 0; i < fallbackKeys.size(); i++) { + msg.appendFormat(", %d->%d", fallbackKeys.keyAt(i), + fallbackKeys.valueAt(i)); + } + ALOGD("Unhandled key event: %d currently tracked fallback keys%s.", + fallbackKeys.size(), msg.string()); + } +#endif + + 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 = fallbackKeyCode; + keyEntry->scanCode = event.getScanCode(); + keyEntry->metaState = event.getMetaState(); + keyEntry->repeatCount = event.getRepeatCount(); + keyEntry->downTime = event.getDownTime(); + keyEntry->syntheticRepeat = false; + +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Unhandled key event: Dispatching fallback key. " + "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x", + originalKeyCode, fallbackKeyCode, keyEntry->metaState); +#endif + return true; // restart the event + } else { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Unhandled key event: No fallback key."); +#endif + } + } + } + return false; +} + +bool InputDispatcher::afterMotionEventLockedInterruptible(const sp& connection, + DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled) { + return false; +} + +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::traceInboundQueueLengthLocked() { + if (ATRACE_ENABLED()) { + ATRACE_INT("iq", mInboundQueue.count()); + } +} + +void InputDispatcher::traceOutboundQueueLengthLocked(const sp& connection) { + if (ATRACE_ENABLED()) { + char counterName[40]; + snprintf(counterName, sizeof(counterName), "oq:%s", connection->getWindowName()); + ATRACE_INT(counterName, connection->outboundQueue.count()); + } +} + +void InputDispatcher::traceWaitQueueLengthLocked(const sp& connection) { + if (ATRACE_ENABLED()) { + char counterName[40]; + snprintf(counterName, sizeof(counterName), "wq:%s", connection->getWindowName()); + ATRACE_INT(counterName, connection->waitQueue.count()); + } +} + +void InputDispatcher::dump(String8& dump) { + AutoMutex _l(mLock); + + dump.append("Input Dispatcher State:\n"); + dumpDispatchStateLocked(dump); + + if (!mLastANRState.isEmpty()) { + dump.append("\nInput Dispatcher State at time of last ANR:\n"); + dump.append(mLastANRState); + } +} + +void InputDispatcher::monitor() { + // Acquire and release the lock to ensure that the dispatcher has not deadlocked. + mLock.lock(); + mLooper->wake(); + mDispatcherIsAliveCondition.wait(mLock); + mLock.unlock(); +} + + +// --- InputDispatcher::Queue --- + +template +uint32_t InputDispatcher::Queue::count() const { + uint32_t result = 0; + for (const T* entry = head; entry; entry = entry->next) { + result += 1; + } + return result; +} + + +// --- InputDispatcher::InjectionState --- + +InputDispatcher::InjectionState::InjectionState(int32_t injectorPid, int32_t injectorUid) : + refCount(1), + injectorPid(injectorPid), injectorUid(injectorUid), + injectionResult(INPUT_EVENT_INJECTION_PENDING), injectionIsAsync(false), + pendingForegroundDispatches(0) { +} + +InputDispatcher::InjectionState::~InjectionState() { +} + +void InputDispatcher::InjectionState::release() { + refCount -= 1; + if (refCount == 0) { + delete this; + } else { + ALOG_ASSERT(refCount > 0); + } +} + + +// --- InputDispatcher::EventEntry --- + +InputDispatcher::EventEntry::EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags) : + refCount(1), type(type), eventTime(eventTime), policyFlags(policyFlags), + injectionState(NULL), dispatchInProgress(false) { +} + +InputDispatcher::EventEntry::~EventEntry() { + releaseInjectionState(); +} + +void InputDispatcher::EventEntry::release() { + refCount -= 1; + if (refCount == 0) { + delete this; + } else { + ALOG_ASSERT(refCount > 0); + } +} + +void InputDispatcher::EventEntry::releaseInjectionState() { + if (injectionState) { + injectionState->release(); + injectionState = NULL; + } +} + + +// --- InputDispatcher::ConfigurationChangedEntry --- + +InputDispatcher::ConfigurationChangedEntry::ConfigurationChangedEntry(nsecs_t eventTime) : + EventEntry(TYPE_CONFIGURATION_CHANGED, eventTime, 0) { +} + +InputDispatcher::ConfigurationChangedEntry::~ConfigurationChangedEntry() { +} + +void InputDispatcher::ConfigurationChangedEntry::appendDescription(String8& msg) const { + msg.append("ConfigurationChangedEvent(), policyFlags=0x%08x", + policyFlags); +} + + +// --- InputDispatcher::DeviceResetEntry --- + +InputDispatcher::DeviceResetEntry::DeviceResetEntry(nsecs_t eventTime, int32_t deviceId) : + EventEntry(TYPE_DEVICE_RESET, eventTime, 0), + deviceId(deviceId) { +} + +InputDispatcher::DeviceResetEntry::~DeviceResetEntry() { +} + +void InputDispatcher::DeviceResetEntry::appendDescription(String8& msg) const { + msg.appendFormat("DeviceResetEvent(deviceId=%d), policyFlags=0x%08x", + deviceId, policyFlags); +} + + +// --- InputDispatcher::KeyEntry --- + +InputDispatcher::KeyEntry::KeyEntry(nsecs_t eventTime, + int32_t deviceId, uint32_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) : + EventEntry(TYPE_KEY, eventTime, policyFlags), + deviceId(deviceId), source(source), action(action), flags(flags), + keyCode(keyCode), scanCode(scanCode), metaState(metaState), + repeatCount(repeatCount), downTime(downTime), + syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN), + interceptKeyWakeupTime(0) { +} + +InputDispatcher::KeyEntry::~KeyEntry() { +} + +void InputDispatcher::KeyEntry::appendDescription(String8& msg) const { + msg.appendFormat("KeyEvent(deviceId=%d, source=0x%08x, action=%d, " + "flags=0x%08x, keyCode=%d, scanCode=%d, metaState=0x%08x, " + "repeatCount=%d), policyFlags=0x%08x", + deviceId, source, action, flags, keyCode, scanCode, metaState, + repeatCount, policyFlags); +} + +void InputDispatcher::KeyEntry::recycle() { + releaseInjectionState(); + + dispatchInProgress = false; + syntheticRepeat = false; + interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + interceptKeyWakeupTime = 0; +} + + +// --- InputDispatcher::MotionEntry --- + +InputDispatcher::MotionEntry::MotionEntry(nsecs_t eventTime, + int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, int32_t flags, + int32_t metaState, int32_t buttonState, + int32_t edgeFlags, float xPrecision, float yPrecision, + nsecs_t downTime, int32_t displayId, uint32_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) : + EventEntry(TYPE_MOTION, eventTime, policyFlags), + eventTime(eventTime), + deviceId(deviceId), source(source), action(action), flags(flags), + metaState(metaState), buttonState(buttonState), edgeFlags(edgeFlags), + xPrecision(xPrecision), yPrecision(yPrecision), + downTime(downTime), displayId(displayId), pointerCount(pointerCount) { + for (uint32_t i = 0; i < pointerCount; i++) { + this->pointerProperties[i].copyFrom(pointerProperties[i]); + this->pointerCoords[i].copyFrom(pointerCoords[i]); + } +} + +InputDispatcher::MotionEntry::~MotionEntry() { +} + +void InputDispatcher::MotionEntry::appendDescription(String8& msg) const { + msg.appendFormat("MotionEvent(deviceId=%d, source=0x%08x, action=%d, " + "flags=0x%08x, metaState=0x%08x, buttonState=0x%08x, edgeFlags=0x%08x, " + "xPrecision=%.1f, yPrecision=%.1f, displayId=%d, pointers=[", + deviceId, source, action, flags, metaState, buttonState, edgeFlags, + xPrecision, yPrecision, displayId); + for (uint32_t i = 0; i < pointerCount; i++) { + if (i) { + msg.append(", "); + } + msg.appendFormat("%d: (%.1f, %.1f)", pointerProperties[i].id, + pointerCoords[i].getX(), pointerCoords[i].getY()); + } + msg.appendFormat("]), policyFlags=0x%08x", policyFlags); +} + + +// --- InputDispatcher::DispatchEntry --- + +volatile int32_t InputDispatcher::DispatchEntry::sNextSeqAtomic; + +InputDispatcher::DispatchEntry::DispatchEntry(EventEntry* eventEntry, + int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) : + seq(nextSeq()), + eventEntry(eventEntry), targetFlags(targetFlags), + xOffset(xOffset), yOffset(yOffset), scaleFactor(scaleFactor), + deliveryTime(0), resolvedAction(0), resolvedFlags(0) { + eventEntry->refCount += 1; +} + +InputDispatcher::DispatchEntry::~DispatchEntry() { + eventEntry->release(); +} + +uint32_t InputDispatcher::DispatchEntry::nextSeq() { + // Sequence number 0 is reserved and will never be returned. + uint32_t seq; + do { + seq = android_atomic_inc(&sNextSeqAtomic); + } while (!seq); + return seq; +} + + +// --- InputDispatcher::InputState --- + +InputDispatcher::InputState::InputState() { +} + +InputDispatcher::InputState::~InputState() { +} + +bool InputDispatcher::InputState::isNeutral() const { + return mKeyMementos.isEmpty() && mMotionMementos.isEmpty(); +} + +bool InputDispatcher::InputState::isHovering(int32_t deviceId, uint32_t source, + int32_t displayId) const { + for (size_t i = 0; i < mMotionMementos.size(); i++) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + if (memento.deviceId == deviceId + && memento.source == source + && memento.displayId == displayId + && memento.hovering) { + return true; + } + } + return false; +} + +bool InputDispatcher::InputState::trackKey(const KeyEntry* entry, + int32_t action, int32_t flags) { + switch (action) { + case AKEY_EVENT_ACTION_UP: { + if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) { + for (size_t i = 0; i < mFallbackKeys.size(); ) { + if (mFallbackKeys.valueAt(i) == entry->keyCode) { + mFallbackKeys.removeItemsAt(i); + } else { + i += 1; + } + } + } + ssize_t index = findKeyMemento(entry); + if (index >= 0) { + mKeyMementos.removeAt(index); + return true; + } + /* FIXME: We can't just drop the key up event because that prevents creating + * popup windows that are automatically shown when a key is held and then + * dismissed when the key is released. The problem is that the popup will + * not have received the original key down, so the key up will be considered + * to be inconsistent with its observed state. We could perhaps handle this + * by synthesizing a key down but that will cause other problems. + * + * So for now, allow inconsistent key up events to be dispatched. + * +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, " + "keyCode=%d, scanCode=%d", + entry->deviceId, entry->source, entry->keyCode, entry->scanCode); +#endif + return false; + */ + return true; + } + + case AKEY_EVENT_ACTION_DOWN: { + ssize_t index = findKeyMemento(entry); + if (index >= 0) { + mKeyMementos.removeAt(index); + } + addKeyMemento(entry, flags); + return true; + } + + default: + return true; + } +} + +bool InputDispatcher::InputState::trackMotion(const MotionEntry* entry, + int32_t action, int32_t flags) { + int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK; + switch (actionMasked) { + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_CANCEL: { + ssize_t index = findMotionMemento(entry, false /*hovering*/); + if (index >= 0) { + mMotionMementos.removeAt(index); + return true; + } +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, " + "actionMasked=%d", + entry->deviceId, entry->source, actionMasked); +#endif + return false; + } + + case AMOTION_EVENT_ACTION_DOWN: { + ssize_t index = findMotionMemento(entry, false /*hovering*/); + if (index >= 0) { + mMotionMementos.removeAt(index); + } + addMotionMemento(entry, flags, false /*hovering*/); + return true; + } + + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + case AMOTION_EVENT_ACTION_MOVE: { + ssize_t index = findMotionMemento(entry, false /*hovering*/); + if (index >= 0) { + MotionMemento& memento = mMotionMementos.editItemAt(index); + memento.setPointers(entry); + return true; + } + if (actionMasked == AMOTION_EVENT_ACTION_MOVE + && (entry->source & (AINPUT_SOURCE_CLASS_JOYSTICK + | AINPUT_SOURCE_CLASS_NAVIGATION))) { + // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP. + return true; + } +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Dropping inconsistent motion pointer up/down or move event: " + "deviceId=%d, source=%08x, actionMasked=%d", + entry->deviceId, entry->source, actionMasked); +#endif + return false; + } + + case AMOTION_EVENT_ACTION_HOVER_EXIT: { + ssize_t index = findMotionMemento(entry, true /*hovering*/); + if (index >= 0) { + mMotionMementos.removeAt(index); + return true; + } +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x", + entry->deviceId, entry->source); +#endif + return false; + } + + case AMOTION_EVENT_ACTION_HOVER_ENTER: + case AMOTION_EVENT_ACTION_HOVER_MOVE: { + ssize_t index = findMotionMemento(entry, true /*hovering*/); + if (index >= 0) { + mMotionMementos.removeAt(index); + } + addMotionMemento(entry, flags, true /*hovering*/); + return true; + } + + default: + return true; + } +} + +ssize_t InputDispatcher::InputState::findKeyMemento(const KeyEntry* entry) const { + for (size_t i = 0; i < mKeyMementos.size(); i++) { + const KeyMemento& memento = mKeyMementos.itemAt(i); + if (memento.deviceId == entry->deviceId + && memento.source == entry->source + && memento.keyCode == entry->keyCode + && memento.scanCode == entry->scanCode) { + return i; + } + } + return -1; +} + +ssize_t InputDispatcher::InputState::findMotionMemento(const MotionEntry* entry, + bool hovering) const { + for (size_t i = 0; i < mMotionMementos.size(); i++) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + if (memento.deviceId == entry->deviceId + && memento.source == entry->source + && memento.displayId == entry->displayId + && memento.hovering == hovering) { + return i; + } + } + return -1; +} + +void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) { + mKeyMementos.push(); + KeyMemento& memento = mKeyMementos.editTop(); + memento.deviceId = entry->deviceId; + memento.source = entry->source; + memento.keyCode = entry->keyCode; + memento.scanCode = entry->scanCode; + memento.metaState = entry->metaState; + memento.flags = flags; + memento.downTime = entry->downTime; + memento.policyFlags = entry->policyFlags; +} + +void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry, + int32_t flags, bool hovering) { + mMotionMementos.push(); + MotionMemento& memento = mMotionMementos.editTop(); + memento.deviceId = entry->deviceId; + memento.source = entry->source; + memento.flags = flags; + memento.xPrecision = entry->xPrecision; + memento.yPrecision = entry->yPrecision; + memento.downTime = entry->downTime; + memento.displayId = entry->displayId; + memento.setPointers(entry); + memento.hovering = hovering; + memento.policyFlags = entry->policyFlags; +} + +void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) { + pointerCount = entry->pointerCount; + for (uint32_t i = 0; i < entry->pointerCount; i++) { + pointerProperties[i].copyFrom(entry->pointerProperties[i]); + pointerCoords[i].copyFrom(entry->pointerCoords[i]); + } +} + +void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime, + Vector& outEvents, const CancelationOptions& options) { + for (size_t i = 0; i < mKeyMementos.size(); i++) { + const KeyMemento& memento = mKeyMementos.itemAt(i); + if (shouldCancelKey(memento, options)) { + outEvents.push(new KeyEntry(currentTime, + memento.deviceId, memento.source, memento.policyFlags, + AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED, + memento.keyCode, memento.scanCode, memento.metaState, 0, memento.downTime)); + } + } + + for (size_t i = 0; i < mMotionMementos.size(); i++) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + if (shouldCancelMotion(memento, options)) { + outEvents.push(new MotionEntry(currentTime, + memento.deviceId, memento.source, memento.policyFlags, + memento.hovering + ? AMOTION_EVENT_ACTION_HOVER_EXIT + : AMOTION_EVENT_ACTION_CANCEL, + memento.flags, 0, 0, 0, + memento.xPrecision, memento.yPrecision, memento.downTime, + memento.displayId, + memento.pointerCount, memento.pointerProperties, memento.pointerCoords)); + } + } +} + +void InputDispatcher::InputState::clear() { + mKeyMementos.clear(); + mMotionMementos.clear(); + mFallbackKeys.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 + && memento.displayId == otherMemento.displayId) { + other.mMotionMementos.removeAt(j); + } else { + j += 1; + } + } + other.mMotionMementos.push(memento); + } + } +} + +int32_t InputDispatcher::InputState::getFallbackKey(int32_t originalKeyCode) { + ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); + return index >= 0 ? mFallbackKeys.valueAt(index) : -1; +} + +void InputDispatcher::InputState::setFallbackKey(int32_t originalKeyCode, + int32_t fallbackKeyCode) { + ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); + if (index >= 0) { + mFallbackKeys.replaceValueAt(index, fallbackKeyCode); + } else { + mFallbackKeys.add(originalKeyCode, fallbackKeyCode); + } +} + +void InputDispatcher::InputState::removeFallbackKey(int32_t originalKeyCode) { + mFallbackKeys.removeItem(originalKeyCode); +} + +bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento, + const CancelationOptions& options) { + if (options.keyCode != -1 && memento.keyCode != options.keyCode) { + return false; + } + + if (options.deviceId != -1 && memento.deviceId != options.deviceId) { + return false; + } + + switch (options.mode) { + case CancelationOptions::CANCEL_ALL_EVENTS: + case CancelationOptions::CANCEL_NON_POINTER_EVENTS: + return true; + case CancelationOptions::CANCEL_FALLBACK_EVENTS: + return memento.flags & AKEY_EVENT_FLAG_FALLBACK; + default: + return false; + } +} + +bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento, + const CancelationOptions& options) { + if (options.deviceId != -1 && memento.deviceId != options.deviceId) { + return false; + } + + switch (options.mode) { + case CancelationOptions::CANCEL_ALL_EVENTS: + return true; + case CancelationOptions::CANCEL_POINTER_EVENTS: + return memento.source & AINPUT_SOURCE_CLASS_POINTER; + case CancelationOptions::CANCEL_NON_POINTER_EVENTS: + return !(memento.source & AINPUT_SOURCE_CLASS_POINTER); + default: + return false; + } +} + + +// --- InputDispatcher::Connection --- + +InputDispatcher::Connection::Connection(const sp& inputChannel, + const sp& inputWindowHandle, bool monitor) : + status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle), + monitor(monitor), + inputPublisher(inputChannel), inputPublisherBlocked(false) { +} + +InputDispatcher::Connection::~Connection() { +} + +const char* InputDispatcher::Connection::getWindowName() const { + if (inputWindowHandle != NULL) { + return inputWindowHandle->getName().string(); + } + if (monitor) { + return "monitor"; + } + return "?"; +} + +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::findWaitQueueEntry(uint32_t seq) { + for (DispatchEntry* entry = waitQueue.head; entry != NULL; entry = entry->next) { + if (entry->seq == seq) { + return entry; + } + } + return NULL; +} + + +// --- InputDispatcher::CommandEntry --- + +InputDispatcher::CommandEntry::CommandEntry(Command command) : + command(command), eventTime(0), keyEntry(NULL), userActivityEventType(0), + seq(0), handled(false) { +} + +InputDispatcher::CommandEntry::~CommandEntry() { +} + + +// --- InputDispatcher::TouchState --- + +InputDispatcher::TouchState::TouchState() : + down(false), split(false), deviceId(-1), source(0), displayId(-1) { +} + +InputDispatcher::TouchState::~TouchState() { +} + +void InputDispatcher::TouchState::reset() { + down = false; + split = false; + deviceId = -1; + source = 0; + displayId = -1; + windows.clear(); +} + +void InputDispatcher::TouchState::copyFrom(const TouchState& other) { + down = other.down; + split = other.split; + deviceId = other.deviceId; + source = other.source; + displayId = other.displayId; + windows = other.windows; +} + +void InputDispatcher::TouchState::addOrUpdateWindow(const sp& windowHandle, + 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.windowHandle == windowHandle) { + touchedWindow.targetFlags |= targetFlags; + if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) { + touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS; + } + touchedWindow.pointerIds.value |= pointerIds.value; + return; + } + } + + windows.push(); + + TouchedWindow& touchedWindow = windows.editTop(); + touchedWindow.windowHandle = windowHandle; + touchedWindow.targetFlags = targetFlags; + touchedWindow.pointerIds = pointerIds; +} + +void InputDispatcher::TouchState::removeWindow(const sp& windowHandle) { + for (size_t i = 0; i < windows.size(); i++) { + if (windows.itemAt(i).windowHandle == windowHandle) { + windows.removeAt(i); + return; + } + } +} + +void InputDispatcher::TouchState::filterNonAsIsTouchWindows() { + for (size_t i = 0 ; i < windows.size(); ) { + TouchedWindow& window = windows.editItemAt(i); + if (window.targetFlags & (InputTarget::FLAG_DISPATCH_AS_IS + | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER)) { + window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK; + window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS; + i += 1; + } else { + windows.removeAt(i); + } + } +} + +sp InputDispatcher::TouchState::getFirstForegroundWindowHandle() const { + for (size_t i = 0; i < windows.size(); i++) { + const TouchedWindow& window = windows.itemAt(i); + if (window.targetFlags & InputTarget::FLAG_FOREGROUND) { + return window.windowHandle; + } + } + return NULL; +} + +bool InputDispatcher::TouchState::isSlippery() const { + // Must have exactly one foreground window. + bool haveSlipperyForegroundWindow = false; + for (size_t i = 0; i < windows.size(); i++) { + const TouchedWindow& window = windows.itemAt(i); + if (window.targetFlags & InputTarget::FLAG_FOREGROUND) { + if (haveSlipperyForegroundWindow + || !(window.windowHandle->getInfo()->layoutParamsFlags + & InputWindowInfo::FLAG_SLIPPERY)) { + return false; + } + haveSlipperyForegroundWindow = true; + } + } + return haveSlipperyForegroundWindow; +} + + +// --- InputDispatcherThread --- + +InputDispatcherThread::InputDispatcherThread(const sp& dispatcher) : + Thread(/*canCallJava*/ true), mDispatcher(dispatcher) { +} + +InputDispatcherThread::~InputDispatcherThread() { +} + +bool InputDispatcherThread::threadLoop() { + mDispatcher->dispatchOnce(); + return true; +} + +} // namespace android diff --git a/services/inputflinger/InputDispatcher.h b/services/inputflinger/InputDispatcher.h new file mode 100644 index 000000000..190e7b221 --- /dev/null +++ b/services/inputflinger/InputDispatcher.h @@ -0,0 +1,1123 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "InputWindow.h" +#include "InputApplication.h" +#include "InputListener.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 = 1 << 0, + + /* 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 = 1 << 1, + + /* This flag indicates that a motion event is being split across multiple windows. */ + FLAG_SPLIT = 1 << 2, + + /* This flag indicates that the pointer coordinates dispatched to the application + * will be zeroed out to avoid revealing information to an application. This is + * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing + * the same UID from watching all touches. */ + FLAG_ZERO_COORDS = 1 << 3, + + /* This flag indicates that the event should be sent as is. + * Should always be set unless the event is to be transmuted. */ + FLAG_DISPATCH_AS_IS = 1 << 8, + + /* 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_DISPATCH_AS_OUTSIDE = 1 << 9, + + /* This flag indicates that a hover sequence is starting in the given window. + * The event is transmuted into ACTION_HOVER_ENTER. */ + FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10, + + /* This flag indicates that a hover event happened outside of a window which handled + * previous hover events, signifying the end of the current hover sequence for that + * window. + * The event is transmuted into ACTION_HOVER_ENTER. */ + FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11, + + /* This flag indicates that the event should be canceled. + * It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips + * outside of a window. */ + FLAG_DISPATCH_AS_SLIPPERY_EXIT = 1 << 12, + + /* This flag indicates that the event should be dispatched as an initial down. + * It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips + * into a new window. */ + FLAG_DISPATCH_AS_SLIPPERY_ENTER = 1 << 13, + + /* Mask for all dispatch modes. */ + FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS + | FLAG_DISPATCH_AS_OUTSIDE + | FLAG_DISPATCH_AS_HOVER_ENTER + | FLAG_DISPATCH_AS_HOVER_EXIT + | FLAG_DISPATCH_AS_SLIPPERY_EXIT + | FLAG_DISPATCH_AS_SLIPPERY_ENTER, + }; + + // The input channel to be targeted. + sp 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; + + // Scaling factor to apply to MotionEvent as it is delivered. + // (ignored for KeyEvents) + float scaleFactor; + + // The subset of pointer ids to include in motion events dispatched to this input target + // if FLAG_SPLIT is set. + BitSet32 pointerIds; +}; + + +/* + * Input dispatcher configuration. + * + * Specifies various options that modify the behavior of the input dispatcher. + * The values provided here are merely defaults. The actual values will come from ViewConfiguration + * and are passed into the dispatcher during initialization. + */ +struct InputDispatcherConfiguration { + // The key repeat initial timeout. + nsecs_t keyRepeatTimeout; + + // The key repeat inter-key delay. + nsecs_t keyRepeatDelay; + + InputDispatcherConfiguration() : + keyRepeatTimeout(500 * 1000000LL), + keyRepeatDelay(50 * 1000000LL) { } +}; + + +/* + * 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, + const sp& inputWindowHandle, + const String8& reason) = 0; + + /* Notifies the system that an input channel is unrecoverably broken. */ + virtual void notifyInputChannelBroken(const sp& inputWindowHandle) = 0; + + /* Gets the input dispatcher configuration. */ + virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0; + + /* Returns true if automatic key repeating is enabled. */ + virtual bool isKeyRepeatEnabled() = 0; + + /* Filters an input event. + * Return true to dispatch the event unmodified, false to consume the event. + * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED + * to injectInputEvent. + */ + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 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 touch, trackball or other motion 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 interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0; + + /* Allows the policy a chance to intercept a key before dispatching. */ + virtual nsecs_t interceptKeyBeforeDispatching(const sp& inputWindowHandle, + 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& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0; + + /* Notifies the policy about switch events. + */ + virtual void notifySwitch(nsecs_t when, + uint32_t switchValues, uint32_t switchMask, 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, public InputListenerInterface { +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; + + /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */ + virtual void monitor() = 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; + + /* 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, + uint32_t policyFlags) = 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 >& inputWindowHandles) = 0; + + /* Sets the focused application. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void setFocusedApplication( + const sp& inputApplicationHandle) = 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; + + /* Sets whether input event filtering is enabled. + * When enabled, incoming input events are sent to the policy's filterInputEvent + * method instead of being dispatched. The filter is expected to use + * injectInputEvent to inject the events it would like to have dispatched. + * It should include POLICY_FLAG_FILTERED in the policy flags during injection. + */ + virtual void setInputFilterEnabled(bool enabled) = 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& fromChannel, + const sp& 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, + const sp& inputWindowHandle, bool monitor) = 0; + virtual status_t unregisterInputChannel(const sp& 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& policy); + + virtual void dump(String8& dump); + virtual void monitor(); + + virtual void dispatchOnce(); + + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args); + virtual void notifyKey(const NotifyKeyArgs* args); + virtual void notifyMotion(const NotifyMotionArgs* args); + virtual void notifySwitch(const NotifySwitchArgs* args); + virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args); + + virtual int32_t injectInputEvent(const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags); + + virtual void setInputWindows(const Vector >& inputWindowHandles); + virtual void setFocusedApplication(const sp& inputApplicationHandle); + virtual void setInputDispatchMode(bool enabled, bool frozen); + virtual void setInputFilterEnabled(bool enabled); + + virtual bool transferTouchFocus(const sp& fromChannel, + const sp& toChannel); + + virtual status_t registerInputChannel(const sp& inputChannel, + const sp& inputWindowHandle, bool monitor); + virtual status_t unregisterInputChannel(const sp& inputChannel); + +private: + template + struct Link { + T* next; + T* prev; + + protected: + inline Link() : next(NULL), prev(NULL) { } + }; + + 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 + + InjectionState(int32_t injectorPid, int32_t injectorUid); + void release(); + + private: + ~InjectionState(); + }; + + struct EventEntry : Link { + enum { + TYPE_CONFIGURATION_CHANGED, + TYPE_DEVICE_RESET, + 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() const { return injectionState != NULL; } + + void release(); + + virtual void appendDescription(String8& msg) const = 0; + + protected: + EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags); + virtual ~EventEntry(); + void releaseInjectionState(); + }; + + struct ConfigurationChangedEntry : EventEntry { + ConfigurationChangedEntry(nsecs_t eventTime); + virtual void appendDescription(String8& msg) const; + + protected: + virtual ~ConfigurationChangedEntry(); + }; + + struct DeviceResetEntry : EventEntry { + int32_t deviceId; + + DeviceResetEntry(nsecs_t eventTime, int32_t deviceId); + virtual void appendDescription(String8& msg) const; + + protected: + virtual ~DeviceResetEntry(); + }; + + struct KeyEntry : EventEntry { + int32_t deviceId; + uint32_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, + INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER, + }; + InterceptKeyResult interceptKeyResult; // set based on the interception result + nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER + + KeyEntry(nsecs_t eventTime, + int32_t deviceId, uint32_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); + virtual void appendDescription(String8& msg) const; + void recycle(); + + protected: + virtual ~KeyEntry(); + }; + + struct MotionEntry : EventEntry { + nsecs_t eventTime; + int32_t deviceId; + uint32_t source; + int32_t action; + int32_t flags; + int32_t metaState; + int32_t buttonState; + int32_t edgeFlags; + float xPrecision; + float yPrecision; + nsecs_t downTime; + int32_t displayId; + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + + MotionEntry(nsecs_t eventTime, + int32_t deviceId, uint32_t source, uint32_t policyFlags, + int32_t action, int32_t flags, + int32_t metaState, int32_t buttonState, int32_t edgeFlags, + float xPrecision, float yPrecision, + nsecs_t downTime, int32_t displayId, uint32_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords); + virtual void appendDescription(String8& msg) const; + + protected: + virtual ~MotionEntry(); + }; + + // Tracks the progress of dispatching a particular event to a particular connection. + struct DispatchEntry : Link { + const uint32_t seq; // unique sequence number, never 0 + + EventEntry* eventEntry; // the event to dispatch + int32_t targetFlags; + float xOffset; + float yOffset; + float scaleFactor; + nsecs_t deliveryTime; // time when the event was actually delivered + + // Set to the resolved action and flags when the event is enqueued. + int32_t resolvedAction; + int32_t resolvedFlags; + + DispatchEntry(EventEntry* eventEntry, + int32_t targetFlags, float xOffset, float yOffset, float scaleFactor); + ~DispatchEntry(); + + inline bool hasForegroundTarget() const { + return targetFlags & InputTarget::FLAG_FOREGROUND; + } + + inline bool isSplit() const { + return targetFlags & InputTarget::FLAG_SPLIT; + } + + private: + static volatile int32_t sNextSeqAtomic; + + static uint32_t nextSeq(); + }; + + // 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(Command command); + ~CommandEntry(); + + Command command; + + // parameters for the command (usage varies by command) + sp connection; + nsecs_t eventTime; + KeyEntry* keyEntry; + sp inputApplicationHandle; + sp inputWindowHandle; + String8 reason; + int32_t userActivityEventType; + uint32_t seq; + bool handled; + }; + + // Generic queue implementation. + template + struct Queue { + T* head; + T* tail; + + inline Queue() : head(NULL), tail(NULL) { + } + + inline bool isEmpty() const { + return !head; + } + + inline void enqueueAtTail(T* entry) { + entry->prev = tail; + if (tail) { + tail->next = entry; + } else { + head = entry; + } + entry->next = NULL; + tail = entry; + } + + inline void enqueueAtHead(T* entry) { + entry->next = head; + if (head) { + head->prev = entry; + } else { + tail = entry; + } + entry->prev = NULL; + head = entry; + } + + inline void dequeue(T* entry) { + if (entry->prev) { + entry->prev->next = entry->next; + } else { + head = entry->next; + } + if (entry->next) { + entry->next->prev = entry->prev; + } else { + tail = entry->prev; + } + } + + inline T* dequeueAtHead() { + T* entry = head; + head = entry->next; + if (head) { + head->prev = NULL; + } else { + tail = NULL; + } + return entry; + } + + uint32_t count() const; + }; + + /* Specifies which events are to be canceled and why. */ + struct CancelationOptions { + enum Mode { + CANCEL_ALL_EVENTS = 0, + CANCEL_POINTER_EVENTS = 1, + CANCEL_NON_POINTER_EVENTS = 2, + CANCEL_FALLBACK_EVENTS = 3, + }; + + // The criterion to use to determine which events should be canceled. + Mode mode; + + // Descriptive reason for the cancelation. + const char* reason; + + // The specific keycode of the key event to cancel, or -1 to cancel any key event. + int32_t keyCode; + + // The specific device id of events to cancel, or -1 to cancel events from any device. + int32_t deviceId; + + CancelationOptions(Mode mode, const char* reason) : + mode(mode), reason(reason), keyCode(-1), deviceId(-1) { } + }; + + /* Tracks dispatched key and motion event state so that cancelation events can be + * synthesized when events are dropped. */ + class InputState { + public: + InputState(); + ~InputState(); + + // Returns true if there is no state to be canceled. + bool isNeutral() const; + + // Returns true if the specified source is known to have received a hover enter + // motion event. + bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const; + + // Records tracking information for a key event that has just been published. + // Returns true if the event should be delivered, false if it is inconsistent + // and should be skipped. + bool trackKey(const KeyEntry* entry, int32_t action, int32_t flags); + + // Records tracking information for a motion event that has just been published. + // Returns true if the event should be delivered, false if it is inconsistent + // and should be skipped. + bool trackMotion(const MotionEntry* entry, int32_t action, int32_t flags); + + // Synthesizes cancelation events for the current state and resets the tracked state. + void synthesizeCancelationEvents(nsecs_t currentTime, + Vector& outEvents, const CancelationOptions& options); + + // Clears the current state. + void clear(); + + // Copies pointer-related parts of the input state to another instance. + void copyPointerStateTo(InputState& other) const; + + // Gets the fallback key associated with a keycode. + // Returns -1 if none. + // Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy. + int32_t getFallbackKey(int32_t originalKeyCode); + + // Sets the fallback key for a particular keycode. + void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode); + + // Removes the fallback key for a particular keycode. + void removeFallbackKey(int32_t originalKeyCode); + + inline const KeyedVector& getFallbackKeys() const { + return mFallbackKeys; + } + + private: + struct KeyMemento { + int32_t deviceId; + uint32_t source; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + int32_t flags; + nsecs_t downTime; + uint32_t policyFlags; + }; + + struct MotionMemento { + int32_t deviceId; + uint32_t source; + int32_t flags; + float xPrecision; + float yPrecision; + nsecs_t downTime; + int32_t displayId; + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + bool hovering; + uint32_t policyFlags; + + void setPointers(const MotionEntry* entry); + }; + + Vector mKeyMementos; + Vector mMotionMementos; + KeyedVector mFallbackKeys; + + ssize_t findKeyMemento(const KeyEntry* entry) const; + ssize_t findMotionMemento(const MotionEntry* entry, bool hovering) const; + + void addKeyMemento(const KeyEntry* entry, int32_t flags); + void addMotionMemento(const MotionEntry* entry, int32_t flags, bool hovering); + + static bool shouldCancelKey(const KeyMemento& memento, + const CancelationOptions& options); + static bool shouldCancelMotion(const MotionMemento& memento, + const 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; // never null + sp inputWindowHandle; // may be null + bool monitor; + InputPublisher inputPublisher; + InputState inputState; + + // True if the socket is full and no further events can be published until + // the application consumes some of the input. + bool inputPublisherBlocked; + + // Queue of events that need to be published to the connection. + Queue outboundQueue; + + // Queue of events that have been published to the connection but that have not + // yet received a "finished" response from the application. + Queue waitQueue; + + explicit Connection(const sp& inputChannel, + const sp& inputWindowHandle, bool monitor); + + inline const char* getInputChannelName() const { return inputChannel->getName().string(); } + + const char* getWindowName() const; + const char* getStatusLabel() const; + + DispatchEntry* findWaitQueueEntry(uint32_t seq); + }; + + enum DropReason { + DROP_REASON_NOT_DROPPED = 0, + DROP_REASON_POLICY = 1, + DROP_REASON_APP_SWITCH = 2, + DROP_REASON_DISABLED = 3, + DROP_REASON_BLOCKED = 4, + DROP_REASON_STALE = 5, + }; + + sp mPolicy; + InputDispatcherConfiguration mConfig; + + Mutex mLock; + + Condition mDispatcherIsAliveCondition; + + sp mLooper; + + EventEntry* mPendingEvent; + Queue mInboundQueue; + Queue mRecentQueue; + Queue mCommandQueue; + + void dispatchOnceInnerLocked(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); + + // Adds an event to a queue of recent events for debugging purposes. + void addRecentEventLocked(EventEntry* entry); + + // 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); + + // Stale event latency optimization. + static bool isStaleEventLocked(nsecs_t currentTime, EventEntry* entry); + + // Blocked event latency optimization. Drops old events when the user intends + // to transfer focus to a new application. + EventEntry* mNextUnblockedEvent; + + sp findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y); + + // All registered connections mapped by channel file descriptor. + KeyedVector > mConnectionsByFd; + + ssize_t getConnectionIndexLocked(const sp& inputChannel); + + // Input channels that will receive a copy of all input events. + Vector > 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); + + // Key repeat tracking. + struct KeyRepeatState { + KeyEntry* lastKeyEntry; // or null if no repeat + nsecs_t nextRepeatTime; + } mKeyRepeatState; + + void resetKeyRepeatLocked(); + KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime); + + // Deferred command processing. + bool haveCommandsLocked() const; + bool runCommandsLockedInterruptible(); + CommandEntry* postCommandLocked(Command command); + + // Input filter processing. + bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args); + bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args); + + // Inbound event processing. + void drainInboundQueueLocked(); + void releasePendingEventLocked(); + void releaseInboundEventLocked(EventEntry* entry); + + // Dispatch state. + bool mDispatchEnabled; + bool mDispatchFrozen; + bool mInputFilterEnabled; + + Vector > mWindowHandles; + + sp getWindowHandleLocked(const sp& inputChannel) const; + bool hasWindowHandleLocked(const sp& windowHandle) const; + + // Focus tracking for keys, trackball, etc. + sp mFocusedWindowHandle; + + // Focus tracking for touch. + struct TouchedWindow { + sp windowHandle; + int32_t targetFlags; + BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set + }; + struct TouchState { + bool down; + bool split; + int32_t deviceId; // id of the device that is currently down, others are rejected + uint32_t source; // source of the device that is current down, others are rejected + int32_t displayId; // id to the display that currently has a touch, others are rejected + Vector windows; + + TouchState(); + ~TouchState(); + void reset(); + void copyFrom(const TouchState& other); + void addOrUpdateWindow(const sp& windowHandle, + int32_t targetFlags, BitSet32 pointerIds); + void removeWindow(const sp& windowHandle); + void filterNonAsIsTouchWindows(); + sp getFirstForegroundWindowHandle() const; + bool isSlippery() const; + }; + + TouchState mTouchState; + TouchState mTempTouchState; + + // Focused application. + sp mFocusedApplicationHandle; + + // Dispatcher state at time of last ANR. + String8 mLastANRState; + + // Dispatch inbound events. + bool dispatchConfigurationChangedLocked( + nsecs_t currentTime, ConfigurationChangedEntry* entry); + bool dispatchDeviceResetLocked( + nsecs_t currentTime, DeviceResetEntry* entry); + bool dispatchKeyLocked( + nsecs_t currentTime, KeyEntry* entry, + DropReason* dropReason, nsecs_t* nextWakeupTime); + bool dispatchMotionLocked( + nsecs_t currentTime, MotionEntry* entry, + DropReason* dropReason, nsecs_t* nextWakeupTime); + void dispatchEventLocked(nsecs_t currentTime, EventEntry* entry, + const Vector& inputTargets); + + void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry); + void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry); + + // Keeping track of ANR timeouts. + 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; + sp mInputTargetWaitApplicationHandle; + + // Contains the last window which received a hover event. + sp mLastHoverWindowHandle; + + // Finding targets for input events. + int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, + const sp& applicationHandle, + const sp& windowHandle, + nsecs_t* nextWakeupTime, const char* reason); + void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout, + const sp& inputChannel); + nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime); + void resetANRTimeoutsLocked(); + + int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, + Vector& inputTargets, nsecs_t* nextWakeupTime); + int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry, + Vector& inputTargets, nsecs_t* nextWakeupTime, + bool* outConflictingPointerActions); + + void addWindowTargetLocked(const sp& windowHandle, + int32_t targetFlags, BitSet32 pointerIds, Vector& inputTargets); + void addMonitoringTargetsLocked(Vector& inputTargets); + + void pokeUserActivityLocked(const EventEntry* eventEntry); + bool checkInjectionPermission(const sp& windowHandle, + const InjectionState* injectionState); + bool isWindowObscuredAtPointLocked(const sp& windowHandle, + int32_t x, int32_t y) const; + bool isWindowReadyForMoreInputLocked(nsecs_t currentTime, + const sp& windowHandle, const EventEntry* eventEntry); + String8 getApplicationWindowLabelLocked(const sp& applicationHandle, + const sp& windowHandle); + + // 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, + EventEntry* eventEntry, const InputTarget* inputTarget); + void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp& connection, + EventEntry* eventEntry, const InputTarget* inputTarget); + void enqueueDispatchEntryLocked(const sp& connection, + EventEntry* eventEntry, const InputTarget* inputTarget, int32_t dispatchMode); + void startDispatchCycleLocked(nsecs_t currentTime, const sp& connection); + void finishDispatchCycleLocked(nsecs_t currentTime, const sp& connection, + uint32_t seq, bool handled); + void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp& connection, + bool notify); + void drainDispatchQueueLocked(Queue* queue); + void releaseDispatchEntryLocked(DispatchEntry* dispatchEntry); + static int handleReceiveCallback(int fd, int events, void* data); + + void synthesizeCancelationEventsForAllConnectionsLocked( + const CancelationOptions& options); + void synthesizeCancelationEventsForInputChannelLocked(const sp& channel, + const CancelationOptions& options); + void synthesizeCancelationEventsForConnectionLocked(const sp& connection, + const CancelationOptions& options); + + // 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(); + + // Registration. + void removeMonitorChannelLocked(const sp& inputChannel); + status_t unregisterInputChannelLocked(const sp& inputChannel, bool notify); + + // 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 onDispatchCycleFinishedLocked( + nsecs_t currentTime, const sp& connection, uint32_t seq, bool handled); + void onDispatchCycleBrokenLocked( + nsecs_t currentTime, const sp& connection); + void onANRLocked( + nsecs_t currentTime, const sp& applicationHandle, + const sp& windowHandle, + nsecs_t eventTime, nsecs_t waitStartTime, const char* reason); + + // Outbound policy interactions. + void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry); + void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry); + void doNotifyANRLockedInterruptible(CommandEntry* commandEntry); + void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry); + void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry); + bool afterKeyEventLockedInterruptible(const sp& connection, + DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled); + bool afterMotionEventLockedInterruptible(const sp& connection, + DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled); + 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); + void traceInboundQueueLengthLocked(); + void traceOutboundQueueLengthLocked(const sp& connection); + void traceWaitQueueLengthLocked(const sp& connection); +}; + +/* Enqueues and dispatches input events, endlessly. */ +class InputDispatcherThread : public Thread { +public: + explicit InputDispatcherThread(const sp& dispatcher); + ~InputDispatcherThread(); + +private: + virtual bool threadLoop(); + + sp mDispatcher; +}; + +} // namespace android + +#endif // _UI_INPUT_DISPATCHER_H diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp new file mode 100644 index 000000000..85bb0ed52 --- /dev/null +++ b/services/inputflinger/InputListener.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2011 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 "InputListener" + +//#define LOG_NDEBUG 0 + +#include "InputListener.h" + +#include + +namespace android { + +// --- NotifyConfigurationChangedArgs --- + +NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(nsecs_t eventTime) : + eventTime(eventTime) { +} + +NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs( + const NotifyConfigurationChangedArgs& other) : + eventTime(other.eventTime) { +} + +void NotifyConfigurationChangedArgs::notify(const sp& listener) const { + listener->notifyConfigurationChanged(this); +} + + +// --- NotifyKeyArgs --- + +NotifyKeyArgs::NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, + int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, + int32_t metaState, nsecs_t downTime) : + eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags), + action(action), flags(flags), keyCode(keyCode), scanCode(scanCode), + metaState(metaState), downTime(downTime) { +} + +NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other) : + eventTime(other.eventTime), deviceId(other.deviceId), source(other.source), + policyFlags(other.policyFlags), + action(other.action), flags(other.flags), + keyCode(other.keyCode), scanCode(other.scanCode), + metaState(other.metaState), downTime(other.downTime) { +} + +void NotifyKeyArgs::notify(const sp& listener) const { + listener->notifyKey(this); +} + + +// --- NotifyMotionArgs --- + +NotifyMotionArgs::NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, + uint32_t policyFlags, + int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, + int32_t edgeFlags, int32_t displayId, uint32_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime) : + eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags), + action(action), flags(flags), metaState(metaState), buttonState(buttonState), + edgeFlags(edgeFlags), displayId(displayId), pointerCount(pointerCount), + xPrecision(xPrecision), yPrecision(yPrecision), downTime(downTime) { + for (uint32_t i = 0; i < pointerCount; i++) { + this->pointerProperties[i].copyFrom(pointerProperties[i]); + this->pointerCoords[i].copyFrom(pointerCoords[i]); + } +} + +NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) : + eventTime(other.eventTime), deviceId(other.deviceId), source(other.source), + policyFlags(other.policyFlags), + action(other.action), flags(other.flags), + metaState(other.metaState), buttonState(other.buttonState), + edgeFlags(other.edgeFlags), displayId(other.displayId), + pointerCount(other.pointerCount), + xPrecision(other.xPrecision), yPrecision(other.yPrecision), downTime(other.downTime) { + for (uint32_t i = 0; i < pointerCount; i++) { + pointerProperties[i].copyFrom(other.pointerProperties[i]); + pointerCoords[i].copyFrom(other.pointerCoords[i]); + } +} + +void NotifyMotionArgs::notify(const sp& listener) const { + listener->notifyMotion(this); +} + + +// --- NotifySwitchArgs --- + +NotifySwitchArgs::NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags, + uint32_t switchValues, uint32_t switchMask) : + eventTime(eventTime), policyFlags(policyFlags), + switchValues(switchValues), switchMask(switchMask) { +} + +NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) : + eventTime(other.eventTime), policyFlags(other.policyFlags), + switchValues(other.switchValues), switchMask(other.switchMask) { +} + +void NotifySwitchArgs::notify(const sp& listener) const { + listener->notifySwitch(this); +} + + +// --- NotifyDeviceResetArgs --- + +NotifyDeviceResetArgs::NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId) : + eventTime(eventTime), deviceId(deviceId) { +} + +NotifyDeviceResetArgs::NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) : + eventTime(other.eventTime), deviceId(other.deviceId) { +} + +void NotifyDeviceResetArgs::notify(const sp& listener) const { + listener->notifyDeviceReset(this); +} + + +// --- QueuedInputListener --- + +QueuedInputListener::QueuedInputListener(const sp& innerListener) : + mInnerListener(innerListener) { +} + +QueuedInputListener::~QueuedInputListener() { + size_t count = mArgsQueue.size(); + for (size_t i = 0; i < count; i++) { + delete mArgsQueue[i]; + } +} + +void QueuedInputListener::notifyConfigurationChanged( + const NotifyConfigurationChangedArgs* args) { + mArgsQueue.push(new NotifyConfigurationChangedArgs(*args)); +} + +void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) { + mArgsQueue.push(new NotifyKeyArgs(*args)); +} + +void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { + mArgsQueue.push(new NotifyMotionArgs(*args)); +} + +void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) { + mArgsQueue.push(new NotifySwitchArgs(*args)); +} + +void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { + mArgsQueue.push(new NotifyDeviceResetArgs(*args)); +} + +void QueuedInputListener::flush() { + size_t count = mArgsQueue.size(); + for (size_t i = 0; i < count; i++) { + NotifyArgs* args = mArgsQueue[i]; + args->notify(mInnerListener); + delete args; + } + mArgsQueue.clear(); +} + + +} // namespace android diff --git a/services/inputflinger/InputListener.h b/services/inputflinger/InputListener.h new file mode 100644 index 000000000..78ae10f70 --- /dev/null +++ b/services/inputflinger/InputListener.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2011 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_LISTENER_H +#define _UI_INPUT_LISTENER_H + +#include +#include +#include + +namespace android { + +class InputListenerInterface; + + +/* Superclass of all input event argument objects */ +struct NotifyArgs { + virtual ~NotifyArgs() { } + + virtual void notify(const sp& listener) const = 0; +}; + + +/* Describes a configuration change event. */ +struct NotifyConfigurationChangedArgs : public NotifyArgs { + nsecs_t eventTime; + + inline NotifyConfigurationChangedArgs() { } + + NotifyConfigurationChangedArgs(nsecs_t eventTime); + + NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other); + + virtual ~NotifyConfigurationChangedArgs() { } + + virtual void notify(const sp& listener) const; +}; + + +/* Describes a key event. */ +struct NotifyKeyArgs : public NotifyArgs { + nsecs_t eventTime; + int32_t deviceId; + uint32_t source; + uint32_t policyFlags; + int32_t action; + int32_t flags; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + nsecs_t downTime; + + inline NotifyKeyArgs() { } + + NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags, + int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, + int32_t metaState, nsecs_t downTime); + + NotifyKeyArgs(const NotifyKeyArgs& other); + + virtual ~NotifyKeyArgs() { } + + virtual void notify(const sp& listener) const; +}; + + +/* Describes a motion event. */ +struct NotifyMotionArgs : public NotifyArgs { + nsecs_t eventTime; + int32_t deviceId; + uint32_t source; + uint32_t policyFlags; + int32_t action; + int32_t flags; + int32_t metaState; + int32_t buttonState; + int32_t edgeFlags; + int32_t displayId; + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + float xPrecision; + float yPrecision; + nsecs_t downTime; + + inline NotifyMotionArgs() { } + + NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags, + int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, + int32_t edgeFlags, int32_t displayId, uint32_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime); + + NotifyMotionArgs(const NotifyMotionArgs& other); + + virtual ~NotifyMotionArgs() { } + + virtual void notify(const sp& listener) const; +}; + + +/* Describes a switch event. */ +struct NotifySwitchArgs : public NotifyArgs { + nsecs_t eventTime; + uint32_t policyFlags; + uint32_t switchValues; + uint32_t switchMask; + + inline NotifySwitchArgs() { } + + NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags, + uint32_t switchValues, uint32_t switchMask); + + NotifySwitchArgs(const NotifySwitchArgs& other); + + virtual ~NotifySwitchArgs() { } + + virtual void notify(const sp& listener) const; +}; + + +/* Describes a device reset event, such as when a device is added, + * reconfigured, or removed. */ +struct NotifyDeviceResetArgs : public NotifyArgs { + nsecs_t eventTime; + int32_t deviceId; + + inline NotifyDeviceResetArgs() { } + + NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId); + + NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other); + + virtual ~NotifyDeviceResetArgs() { } + + virtual void notify(const sp& listener) const; +}; + + +/* + * The interface used by the InputReader to notify the InputListener about input events. + */ +class InputListenerInterface : public virtual RefBase { +protected: + InputListenerInterface() { } + virtual ~InputListenerInterface() { } + +public: + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0; + virtual void notifyKey(const NotifyKeyArgs* args) = 0; + virtual void notifyMotion(const NotifyMotionArgs* args) = 0; + virtual void notifySwitch(const NotifySwitchArgs* args) = 0; + virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0; +}; + + +/* + * An implementation of the listener interface that queues up and defers dispatch + * of decoded events until flushed. + */ +class QueuedInputListener : public InputListenerInterface { +protected: + virtual ~QueuedInputListener(); + +public: + QueuedInputListener(const sp& innerListener); + + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args); + virtual void notifyKey(const NotifyKeyArgs* args); + virtual void notifyMotion(const NotifyMotionArgs* args); + virtual void notifySwitch(const NotifySwitchArgs* args); + virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args); + + void flush(); + +private: + sp mInnerListener; + Vector mArgsQueue; +}; + +} // namespace android + +#endif // _UI_INPUT_LISTENER_H diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp new file mode 100644 index 000000000..6a6547bdf --- /dev/null +++ b/services/inputflinger/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 + +namespace android { + +InputManager::InputManager( + const sp& eventHub, + const sp& readerPolicy, + const sp& dispatcherPolicy) { + mDispatcher = new InputDispatcher(dispatcherPolicy); + mReader = new InputReader(eventHub, readerPolicy, mDispatcher); + initialize(); +} + +InputManager::InputManager( + const sp& reader, + const sp& 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) { + ALOGE("Could not start InputDispatcher thread due to error %d.", result); + return result; + } + + result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); + if (result) { + ALOGE("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) { + ALOGW("Could not stop InputReader thread due to error %d.", result); + } + + result = mDispatcherThread->requestExitAndWait(); + if (result) { + ALOGW("Could not stop InputDispatcher thread due to error %d.", result); + } + + return OK; +} + +sp InputManager::getReader() { + return mReader; +} + +sp InputManager::getDispatcher() { + return mDispatcher; +} + +} // namespace android diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h new file mode 100644 index 000000000..a213b2dfa --- /dev/null +++ b/services/inputflinger/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 +#include +#include +#include +#include +#include +#include + +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 getReader() = 0; + + /* Gets the input dispatcher. */ + virtual sp getDispatcher() = 0; +}; + +class InputManager : public InputManagerInterface { +protected: + virtual ~InputManager(); + +public: + InputManager( + const sp& eventHub, + const sp& readerPolicy, + const sp& dispatcherPolicy); + + // (used for testing purposes) + InputManager( + const sp& reader, + const sp& dispatcher); + + virtual status_t start(); + virtual status_t stop(); + + virtual sp getReader(); + virtual sp getDispatcher(); + +private: + sp mReader; + sp mReaderThread; + + sp mDispatcher; + sp mDispatcherThread; + + void initialize(); +}; + +} // namespace android + +#endif // _UI_INPUT_MANAGER_H diff --git a/services/inputflinger/InputReader.cpp b/services/inputflinger/InputReader.cpp new file mode 100644 index 000000000..94e2a809e --- /dev/null +++ b/services/inputflinger/InputReader.cpp @@ -0,0 +1,6530 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "InputReader" + +//#define LOG_NDEBUG 0 + +// Log debug messages for each raw event received from the EventHub. +#define DEBUG_RAW_EVENTS 0 + +// Log debug messages about touch screen filtering hacks. +#define DEBUG_HACKS 0 + +// Log debug messages about virtual key processing. +#define DEBUG_VIRTUAL_KEYS 0 + +// Log debug messages about pointers. +#define DEBUG_POINTERS 0 + +// Log debug messages about pointer assignment calculations. +#define DEBUG_POINTER_ASSIGNMENT 0 + +// Log debug messages about gesture detection. +#define DEBUG_GESTURES 0 + +// Log debug messages about the vibrator. +#define DEBUG_VIBRATOR 0 + +#include "InputReader.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define INDENT " " +#define INDENT2 " " +#define INDENT3 " " +#define INDENT4 " " +#define INDENT5 " " + +namespace android { + +// --- Constants --- + +// Maximum number of slots supported when using the slot-based Multitouch Protocol B. +static const size_t MAX_SLOTS = 32; + +// --- Static Functions --- + +template +inline static T abs(const T& value) { + return value < 0 ? - value : value; +} + +template +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +template +inline static void swap(T& a, T& b) { + T temp = a; + a = b; + b = temp; +} + +inline static float avg(float x, float y) { + return (x + y) / 2; +} + +inline static float distance(float x1, float y1, float x2, float y2) { + return hypotf(x1 - x2, y1 - y2); +} + +inline static int32_t signExtendNybble(int32_t value) { + return value >= 8 ? value - 16 : value; +} + +static inline const char* toString(bool value) { + return value ? "true" : "false"; +} + +static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation, + const int32_t map[][4], size_t mapSize) { + if (orientation != DISPLAY_ORIENTATION_0) { + for (size_t i = 0; i < mapSize; i++) { + if (value == map[i][0]) { + return map[i][orientation]; + } + } + } + return value; +} + +static const int32_t keyCodeRotationMap[][4] = { + // key codes enumerated counter-clockwise with the original (unrotated) key first + // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation + { AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT }, + { AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN }, + { AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT }, + { AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP }, +}; +static const size_t keyCodeRotationMapSize = + sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); + +static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { + return rotateValueUsingRotationMap(keyCode, orientation, + keyCodeRotationMap, keyCodeRotationMapSize); +} + +static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) { + float temp; + switch (orientation) { + case DISPLAY_ORIENTATION_90: + temp = *deltaX; + *deltaX = *deltaY; + *deltaY = -temp; + break; + + case DISPLAY_ORIENTATION_180: + *deltaX = -*deltaX; + *deltaY = -*deltaY; + break; + + case DISPLAY_ORIENTATION_270: + temp = *deltaX; + *deltaX = -*deltaY; + *deltaY = temp; + break; + } +} + +static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) { + return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0; +} + +// Returns true if the pointer should be reported as being down given the specified +// button states. This determines whether the event is reported as a touch event. +static bool isPointerDown(int32_t buttonState) { + return buttonState & + (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY + | AMOTION_EVENT_BUTTON_TERTIARY); +} + +static float calculateCommonVector(float a, float b) { + if (a > 0 && b > 0) { + return a < b ? a : b; + } else if (a < 0 && b < 0) { + return a > b ? a : b; + } else { + return 0; + } +} + +static void synthesizeButtonKey(InputReaderContext* context, int32_t action, + nsecs_t when, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState, + int32_t buttonState, int32_t keyCode) { + if ( + (action == AKEY_EVENT_ACTION_DOWN + && !(lastButtonState & buttonState) + && (currentButtonState & buttonState)) + || (action == AKEY_EVENT_ACTION_UP + && (lastButtonState & buttonState) + && !(currentButtonState & buttonState))) { + NotifyKeyArgs args(when, deviceId, source, policyFlags, + action, 0, keyCode, 0, context->getGlobalMetaState(), when); + context->getListener()->notifyKey(&args); + } +} + +static void synthesizeButtonKeys(InputReaderContext* context, int32_t action, + nsecs_t when, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState) { + synthesizeButtonKey(context, action, when, deviceId, source, policyFlags, + lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK); + synthesizeButtonKey(context, action, when, deviceId, source, policyFlags, + lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD); +} + + +// --- InputReaderConfiguration --- + +bool InputReaderConfiguration::getDisplayInfo(bool external, DisplayViewport* outViewport) const { + const DisplayViewport& viewport = external ? mExternalDisplay : mInternalDisplay; + if (viewport.displayId >= 0) { + *outViewport = viewport; + return true; + } + return false; +} + +void InputReaderConfiguration::setDisplayInfo(bool external, const DisplayViewport& viewport) { + DisplayViewport& v = external ? mExternalDisplay : mInternalDisplay; + v = viewport; +} + + +// --- InputReader --- + +InputReader::InputReader(const sp& eventHub, + const sp& policy, + const sp& listener) : + mContext(this), mEventHub(eventHub), mPolicy(policy), + mGlobalMetaState(0), mGeneration(1), + mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX), + mConfigurationChangesToRefresh(0) { + mQueuedListener = new QueuedInputListener(listener); + + { // acquire lock + AutoMutex _l(mLock); + + refreshConfigurationLocked(0); + updateGlobalMetaStateLocked(); + } // release lock +} + +InputReader::~InputReader() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } +} + +void InputReader::loopOnce() { + int32_t oldGeneration; + int32_t timeoutMillis; + bool inputDevicesChanged = false; + Vector inputDevices; + { // acquire lock + AutoMutex _l(mLock); + + oldGeneration = mGeneration; + timeoutMillis = -1; + + uint32_t changes = mConfigurationChangesToRefresh; + if (changes) { + mConfigurationChangesToRefresh = 0; + timeoutMillis = 0; + refreshConfigurationLocked(changes); + } else if (mNextTimeout != LLONG_MAX) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout); + } + } // release lock + + size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); + + { // acquire lock + AutoMutex _l(mLock); + mReaderIsAliveCondition.broadcast(); + + if (count) { + processEventsLocked(mEventBuffer, count); + } + + if (mNextTimeout != LLONG_MAX) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + if (now >= mNextTimeout) { +#if DEBUG_RAW_EVENTS + ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); +#endif + mNextTimeout = LLONG_MAX; + timeoutExpiredLocked(now); + } + } + + if (oldGeneration != mGeneration) { + inputDevicesChanged = true; + getInputDevicesLocked(inputDevices); + } + } // release lock + + // Send out a message that the describes the changed input devices. + if (inputDevicesChanged) { + mPolicy->notifyInputDevicesChanged(inputDevices); + } + + // Flush queued events out to the listener. + // This must happen outside of the lock because the listener could potentially call + // back into the InputReader's methods, such as getScanCodeState, or become blocked + // on another thread similarly waiting to acquire the InputReader lock thereby + // resulting in a deadlock. This situation is actually quite plausible because the + // listener is actually the input dispatcher, which calls into the window manager, + // which occasionally calls into the input reader. + mQueuedListener->flush(); +} + +void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { + for (const RawEvent* rawEvent = rawEvents; count;) { + int32_t type = rawEvent->type; + size_t batchSize = 1; + if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) { + int32_t deviceId = rawEvent->deviceId; + while (batchSize < count) { + if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT + || rawEvent[batchSize].deviceId != deviceId) { + break; + } + batchSize += 1; + } +#if DEBUG_RAW_EVENTS + ALOGD("BatchSize: %d Count: %d", batchSize, count); +#endif + processEventsForDeviceLocked(deviceId, rawEvent, batchSize); + } else { + switch (rawEvent->type) { + case EventHubInterface::DEVICE_ADDED: + addDeviceLocked(rawEvent->when, rawEvent->deviceId); + break; + case EventHubInterface::DEVICE_REMOVED: + removeDeviceLocked(rawEvent->when, rawEvent->deviceId); + break; + case EventHubInterface::FINISHED_DEVICE_SCAN: + handleConfigurationChangedLocked(rawEvent->when); + break; + default: + ALOG_ASSERT(false); // can't happen + break; + } + } + count -= batchSize; + rawEvent += batchSize; + } +} + +void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId); + return; + } + + InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId); + uint32_t classes = mEventHub->getDeviceClasses(deviceId); + int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId); + + InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes); + device->configure(when, &mConfig, 0); + device->reset(when); + + if (device->isIgnored()) { + ALOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId, + identifier.name.string()); + } else { + ALOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId, + identifier.name.string(), device->getSources()); + } + + mDevices.add(deviceId, device); + bumpGenerationLocked(); +} + +void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) { + InputDevice* device = NULL; + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId); + return; + } + + device = mDevices.valueAt(deviceIndex); + mDevices.removeItemsAt(deviceIndex, 1); + bumpGenerationLocked(); + + if (device->isIgnored()) { + ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)", + device->getId(), device->getName().string()); + } else { + ALOGI("Device removed: id=%d, name='%s', sources=0x%08x", + device->getId(), device->getName().string(), device->getSources()); + } + + device->reset(when); + delete device; +} + +InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber, + const InputDeviceIdentifier& identifier, uint32_t classes) { + InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(), + controllerNumber, identifier, classes); + + // External devices. + if (classes & INPUT_DEVICE_CLASS_EXTERNAL) { + device->setExternal(true); + } + + // Switch-like devices. + if (classes & INPUT_DEVICE_CLASS_SWITCH) { + device->addMapper(new SwitchInputMapper(device)); + } + + // Vibrator-like devices. + if (classes & INPUT_DEVICE_CLASS_VIBRATOR) { + device->addMapper(new VibratorInputMapper(device)); + } + + // Keyboard-like devices. + uint32_t keyboardSource = 0; + int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC; + if (classes & INPUT_DEVICE_CLASS_KEYBOARD) { + keyboardSource |= AINPUT_SOURCE_KEYBOARD; + } + if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) { + keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC; + } + if (classes & INPUT_DEVICE_CLASS_DPAD) { + keyboardSource |= AINPUT_SOURCE_DPAD; + } + if (classes & INPUT_DEVICE_CLASS_GAMEPAD) { + keyboardSource |= AINPUT_SOURCE_GAMEPAD; + } + + if (keyboardSource != 0) { + device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType)); + } + + // Cursor-like devices. + if (classes & INPUT_DEVICE_CLASS_CURSOR) { + device->addMapper(new CursorInputMapper(device)); + } + + // Touchscreens and touchpad devices. + if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) { + device->addMapper(new MultiTouchInputMapper(device)); + } else if (classes & INPUT_DEVICE_CLASS_TOUCH) { + device->addMapper(new SingleTouchInputMapper(device)); + } + + // Joystick-like devices. + if (classes & INPUT_DEVICE_CLASS_JOYSTICK) { + device->addMapper(new JoystickInputMapper(device)); + } + + return device; +} + +void InputReader::processEventsForDeviceLocked(int32_t deviceId, + const RawEvent* rawEvents, size_t count) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + ALOGW("Discarding event for unknown deviceId %d.", deviceId); + return; + } + + InputDevice* device = mDevices.valueAt(deviceIndex); + if (device->isIgnored()) { + //ALOGD("Discarding event for ignored deviceId %d.", deviceId); + return; + } + + device->process(rawEvents, count); +} + +void InputReader::timeoutExpiredLocked(nsecs_t when) { + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + if (!device->isIgnored()) { + device->timeoutExpired(when); + } + } +} + +void InputReader::handleConfigurationChangedLocked(nsecs_t when) { + // Reset global meta state because it depends on the list of all configured devices. + updateGlobalMetaStateLocked(); + + // Enqueue configuration changed. + NotifyConfigurationChangedArgs args(when); + mQueuedListener->notifyConfigurationChanged(&args); +} + +void InputReader::refreshConfigurationLocked(uint32_t changes) { + mPolicy->getReaderConfiguration(&mConfig); + mEventHub->setExcludedDevices(mConfig.excludedDeviceNames); + + if (changes) { + ALOGI("Reconfiguring input devices. changes=0x%08x", changes); + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + + if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) { + mEventHub->requestReopenDevices(); + } else { + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + device->configure(now, &mConfig, changes); + } + } + } +} + +void InputReader::updateGlobalMetaStateLocked() { + mGlobalMetaState = 0; + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + mGlobalMetaState |= device->getMetaState(); + } +} + +int32_t InputReader::getGlobalMetaStateLocked() { + return mGlobalMetaState; +} + +void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) { + mDisableVirtualKeysTimeout = time; +} + +bool InputReader::shouldDropVirtualKeyLocked(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode) { + if (now < mDisableVirtualKeysTimeout) { + ALOGI("Dropping virtual key from device %s because virtual keys are " + "temporarily disabled for the next %0.3fms. keyCode=%d, scanCode=%d", + device->getName().string(), + (mDisableVirtualKeysTimeout - now) * 0.000001, + keyCode, scanCode); + return true; + } else { + return false; + } +} + +void InputReader::fadePointerLocked() { + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + device->fadePointer(); + } +} + +void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) { + if (when < mNextTimeout) { + mNextTimeout = when; + mEventHub->wake(); + } +} + +int32_t InputReader::bumpGenerationLocked() { + return ++mGeneration; +} + +void InputReader::getInputDevices(Vector& outInputDevices) { + AutoMutex _l(mLock); + getInputDevicesLocked(outInputDevices); +} + +void InputReader::getInputDevicesLocked(Vector& outInputDevices) { + outInputDevices.clear(); + + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (!device->isIgnored()) { + outInputDevices.push(); + device->getDeviceInfo(&outInputDevices.editTop()); + } + } +} + +int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode) { + AutoMutex _l(mLock); + + return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState); +} + +int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode) { + AutoMutex _l(mLock); + + return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState); +} + +int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) { + AutoMutex _l(mLock); + + return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState); +} + +int32_t InputReader::getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code, + GetStateFunc getStateFunc) { + int32_t result = AKEY_STATE_UNKNOWN; + if (deviceId >= 0) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = (device->*getStateFunc)(sourceMask, code); + } + } + } else { + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + // If any device reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that + // value. Otherwise, return AKEY_STATE_UP as long as one device reports it. + int32_t currentResult = (device->*getStateFunc)(sourceMask, code); + if (currentResult >= AKEY_STATE_DOWN) { + return currentResult; + } else if (currentResult == AKEY_STATE_UP) { + result = currentResult; + } + } + } + } + return result; +} + +bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) { + AutoMutex _l(mLock); + + memset(outFlags, 0, numCodes); + return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags); +} + +bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) { + bool result = false; + if (deviceId >= 0) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = device->markSupportedKeyCodes(sourceMask, + numCodes, keyCodes, outFlags); + } + } + } else { + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result |= device->markSupportedKeyCodes(sourceMask, + numCodes, keyCodes, outFlags); + } + } + } + return result; +} + +void InputReader::requestRefreshConfiguration(uint32_t changes) { + AutoMutex _l(mLock); + + if (changes) { + bool needWake = !mConfigurationChangesToRefresh; + mConfigurationChangesToRefresh |= changes; + + if (needWake) { + mEventHub->wake(); + } + } +} + +void InputReader::vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize, + ssize_t repeat, int32_t token) { + AutoMutex _l(mLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + device->vibrate(pattern, patternSize, repeat, token); + } +} + +void InputReader::cancelVibrate(int32_t deviceId, int32_t token) { + AutoMutex _l(mLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + device->cancelVibrate(token); + } +} + +void InputReader::dump(String8& dump) { + AutoMutex _l(mLock); + + mEventHub->dump(dump); + dump.append("\n"); + + dump.append("Input Reader State:\n"); + + for (size_t i = 0; i < mDevices.size(); i++) { + mDevices.valueAt(i)->dump(dump); + } + + dump.append(INDENT "Configuration:\n"); + dump.append(INDENT2 "ExcludedDeviceNames: ["); + for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) { + if (i != 0) { + dump.append(", "); + } + dump.append(mConfig.excludedDeviceNames.itemAt(i).string()); + } + dump.append("]\n"); + dump.appendFormat(INDENT2 "VirtualKeyQuietTime: %0.1fms\n", + mConfig.virtualKeyQuietTime * 0.000001f); + + dump.appendFormat(INDENT2 "PointerVelocityControlParameters: " + "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n", + mConfig.pointerVelocityControlParameters.scale, + mConfig.pointerVelocityControlParameters.lowThreshold, + mConfig.pointerVelocityControlParameters.highThreshold, + mConfig.pointerVelocityControlParameters.acceleration); + + dump.appendFormat(INDENT2 "WheelVelocityControlParameters: " + "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n", + mConfig.wheelVelocityControlParameters.scale, + mConfig.wheelVelocityControlParameters.lowThreshold, + mConfig.wheelVelocityControlParameters.highThreshold, + mConfig.wheelVelocityControlParameters.acceleration); + + dump.appendFormat(INDENT2 "PointerGesture:\n"); + dump.appendFormat(INDENT3 "Enabled: %s\n", + toString(mConfig.pointerGesturesEnabled)); + dump.appendFormat(INDENT3 "QuietInterval: %0.1fms\n", + mConfig.pointerGestureQuietInterval * 0.000001f); + dump.appendFormat(INDENT3 "DragMinSwitchSpeed: %0.1fpx/s\n", + mConfig.pointerGestureDragMinSwitchSpeed); + dump.appendFormat(INDENT3 "TapInterval: %0.1fms\n", + mConfig.pointerGestureTapInterval * 0.000001f); + dump.appendFormat(INDENT3 "TapDragInterval: %0.1fms\n", + mConfig.pointerGestureTapDragInterval * 0.000001f); + dump.appendFormat(INDENT3 "TapSlop: %0.1fpx\n", + mConfig.pointerGestureTapSlop); + dump.appendFormat(INDENT3 "MultitouchSettleInterval: %0.1fms\n", + mConfig.pointerGestureMultitouchSettleInterval * 0.000001f); + dump.appendFormat(INDENT3 "MultitouchMinDistance: %0.1fpx\n", + mConfig.pointerGestureMultitouchMinDistance); + dump.appendFormat(INDENT3 "SwipeTransitionAngleCosine: %0.1f\n", + mConfig.pointerGestureSwipeTransitionAngleCosine); + dump.appendFormat(INDENT3 "SwipeMaxWidthRatio: %0.1f\n", + mConfig.pointerGestureSwipeMaxWidthRatio); + dump.appendFormat(INDENT3 "MovementSpeedRatio: %0.1f\n", + mConfig.pointerGestureMovementSpeedRatio); + dump.appendFormat(INDENT3 "ZoomSpeedRatio: %0.1f\n", + mConfig.pointerGestureZoomSpeedRatio); +} + +void InputReader::monitor() { + // Acquire and release the lock to ensure that the reader has not deadlocked. + mLock.lock(); + mEventHub->wake(); + mReaderIsAliveCondition.wait(mLock); + mLock.unlock(); + + // Check the EventHub + mEventHub->monitor(); +} + + +// --- InputReader::ContextImpl --- + +InputReader::ContextImpl::ContextImpl(InputReader* reader) : + mReader(reader) { +} + +void InputReader::ContextImpl::updateGlobalMetaState() { + // lock is already held by the input loop + mReader->updateGlobalMetaStateLocked(); +} + +int32_t InputReader::ContextImpl::getGlobalMetaState() { + // lock is already held by the input loop + return mReader->getGlobalMetaStateLocked(); +} + +void InputReader::ContextImpl::disableVirtualKeysUntil(nsecs_t time) { + // lock is already held by the input loop + mReader->disableVirtualKeysUntilLocked(time); +} + +bool InputReader::ContextImpl::shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode) { + // lock is already held by the input loop + return mReader->shouldDropVirtualKeyLocked(now, device, keyCode, scanCode); +} + +void InputReader::ContextImpl::fadePointer() { + // lock is already held by the input loop + mReader->fadePointerLocked(); +} + +void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) { + // lock is already held by the input loop + mReader->requestTimeoutAtTimeLocked(when); +} + +int32_t InputReader::ContextImpl::bumpGeneration() { + // lock is already held by the input loop + return mReader->bumpGenerationLocked(); +} + +InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() { + return mReader->mPolicy.get(); +} + +InputListenerInterface* InputReader::ContextImpl::getListener() { + return mReader->mQueuedListener.get(); +} + +EventHubInterface* InputReader::ContextImpl::getEventHub() { + return mReader->mEventHub.get(); +} + + +// --- InputReaderThread --- + +InputReaderThread::InputReaderThread(const sp& reader) : + Thread(/*canCallJava*/ true), mReader(reader) { +} + +InputReaderThread::~InputReaderThread() { +} + +bool InputReaderThread::threadLoop() { + mReader->loopOnce(); + return true; +} + + +// --- InputDevice --- + +InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation, + int32_t controllerNumber, const InputDeviceIdentifier& identifier, uint32_t classes) : + mContext(context), mId(id), mGeneration(generation), mControllerNumber(controllerNumber), + mIdentifier(identifier), mClasses(classes), + mSources(0), mIsExternal(false), mDropUntilNextSync(false) { +} + +InputDevice::~InputDevice() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + delete mMappers[i]; + } + mMappers.clear(); +} + +void InputDevice::dump(String8& dump) { + InputDeviceInfo deviceInfo; + getDeviceInfo(& deviceInfo); + + dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(), + deviceInfo.getDisplayName().string()); + dump.appendFormat(INDENT2 "Generation: %d\n", mGeneration); + dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal)); + dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources()); + dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType()); + + const Vector& ranges = deviceInfo.getMotionRanges(); + if (!ranges.isEmpty()) { + dump.append(INDENT2 "Motion Ranges:\n"); + for (size_t i = 0; i < ranges.size(); i++) { + const InputDeviceInfo::MotionRange& range = ranges.itemAt(i); + const char* label = getAxisLabel(range.axis); + char name[32]; + if (label) { + strncpy(name, label, sizeof(name)); + name[sizeof(name) - 1] = '\0'; + } else { + snprintf(name, sizeof(name), "%d", range.axis); + } + dump.appendFormat(INDENT3 "%s: source=0x%08x, " + "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, resolution=%0.3f\n", + name, range.source, range.min, range.max, range.flat, range.fuzz, + range.resolution); + } + } + + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->dump(dump); + } +} + +void InputDevice::addMapper(InputMapper* mapper) { + mMappers.add(mapper); +} + +void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { + mSources = 0; + + if (!isIgnored()) { + if (!changes) { // first time only + mContext->getEventHub()->getConfiguration(mId, &mConfiguration); + } + + if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) { + if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) { + sp keyboardLayout = + mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier); + if (mContext->getEventHub()->setKeyboardLayoutOverlay(mId, keyboardLayout)) { + bumpGeneration(); + } + } + } + + if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) { + if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) { + String8 alias = mContext->getPolicy()->getDeviceAlias(mIdentifier); + if (mAlias != alias) { + mAlias = alias; + bumpGeneration(); + } + } + } + + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->configure(when, config, changes); + mSources |= mapper->getSources(); + } + } +} + +void InputDevice::reset(nsecs_t when) { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->reset(when); + } + + mContext->updateGlobalMetaState(); + + notifyReset(when); +} + +void InputDevice::process(const RawEvent* rawEvents, size_t count) { + // Process all of the events in order for each mapper. + // We cannot simply ask each mapper to process them in bulk because mappers may + // have side-effects that must be interleaved. For example, joystick movement events and + // gamepad button presses are handled by different mappers but they should be dispatched + // in the order received. + size_t numMappers = mMappers.size(); + for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) { +#if DEBUG_RAW_EVENTS + ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%lld", + rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value, + rawEvent->when); +#endif + + if (mDropUntilNextSync) { + if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + mDropUntilNextSync = false; +#if DEBUG_RAW_EVENTS + ALOGD("Recovered from input event buffer overrun."); +#endif + } else { +#if DEBUG_RAW_EVENTS + ALOGD("Dropped input event while waiting for next input sync."); +#endif + } + } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) { + ALOGI("Detected input event buffer overrun for device %s.", getName().string()); + mDropUntilNextSync = true; + reset(rawEvent->when); + } else { + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->process(rawEvent); + } + } + } +} + +void InputDevice::timeoutExpired(nsecs_t when) { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->timeoutExpired(when); + } +} + +void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) { + outDeviceInfo->initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, + mIsExternal); + + 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)) { + // If any mapper reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that + // value. Otherwise, return AKEY_STATE_UP as long as one mapper reports it. + int32_t currentResult = (mapper->*getStateFunc)(sourceMask, code); + if (currentResult >= AKEY_STATE_DOWN) { + return currentResult; + } else if (currentResult == AKEY_STATE_UP) { + result = currentResult; + } + } + } + return result; +} + +bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + bool result = false; + 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; +} + +void InputDevice::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, + int32_t token) { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->vibrate(pattern, patternSize, repeat, token); + } +} + +void InputDevice::cancelVibrate(int32_t token) { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->cancelVibrate(token); + } +} + +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; +} + +void InputDevice::fadePointer() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->fadePointer(); + } +} + +void InputDevice::bumpGeneration() { + mGeneration = mContext->bumpGeneration(); +} + +void InputDevice::notifyReset(nsecs_t when) { + NotifyDeviceResetArgs args(when, mId); + mContext->getListener()->notifyDeviceReset(&args); +} + + +// --- CursorButtonAccumulator --- + +CursorButtonAccumulator::CursorButtonAccumulator() { + clearButtons(); +} + +void CursorButtonAccumulator::reset(InputDevice* device) { + mBtnLeft = device->isKeyPressed(BTN_LEFT); + mBtnRight = device->isKeyPressed(BTN_RIGHT); + mBtnMiddle = device->isKeyPressed(BTN_MIDDLE); + mBtnBack = device->isKeyPressed(BTN_BACK); + mBtnSide = device->isKeyPressed(BTN_SIDE); + mBtnForward = device->isKeyPressed(BTN_FORWARD); + mBtnExtra = device->isKeyPressed(BTN_EXTRA); + mBtnTask = device->isKeyPressed(BTN_TASK); +} + +void CursorButtonAccumulator::clearButtons() { + mBtnLeft = 0; + mBtnRight = 0; + mBtnMiddle = 0; + mBtnBack = 0; + mBtnSide = 0; + mBtnForward = 0; + mBtnExtra = 0; + mBtnTask = 0; +} + +void CursorButtonAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_KEY) { + switch (rawEvent->code) { + case BTN_LEFT: + mBtnLeft = rawEvent->value; + break; + case BTN_RIGHT: + mBtnRight = rawEvent->value; + break; + case BTN_MIDDLE: + mBtnMiddle = rawEvent->value; + break; + case BTN_BACK: + mBtnBack = rawEvent->value; + break; + case BTN_SIDE: + mBtnSide = rawEvent->value; + break; + case BTN_FORWARD: + mBtnForward = rawEvent->value; + break; + case BTN_EXTRA: + mBtnExtra = rawEvent->value; + break; + case BTN_TASK: + mBtnTask = rawEvent->value; + break; + } + } +} + +uint32_t CursorButtonAccumulator::getButtonState() const { + uint32_t result = 0; + if (mBtnLeft) { + result |= AMOTION_EVENT_BUTTON_PRIMARY; + } + if (mBtnRight) { + result |= AMOTION_EVENT_BUTTON_SECONDARY; + } + if (mBtnMiddle) { + result |= AMOTION_EVENT_BUTTON_TERTIARY; + } + if (mBtnBack || mBtnSide) { + result |= AMOTION_EVENT_BUTTON_BACK; + } + if (mBtnForward || mBtnExtra) { + result |= AMOTION_EVENT_BUTTON_FORWARD; + } + return result; +} + + +// --- CursorMotionAccumulator --- + +CursorMotionAccumulator::CursorMotionAccumulator() { + clearRelativeAxes(); +} + +void CursorMotionAccumulator::reset(InputDevice* device) { + clearRelativeAxes(); +} + +void CursorMotionAccumulator::clearRelativeAxes() { + mRelX = 0; + mRelY = 0; +} + +void CursorMotionAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_REL) { + switch (rawEvent->code) { + case REL_X: + mRelX = rawEvent->value; + break; + case REL_Y: + mRelY = rawEvent->value; + break; + } + } +} + +void CursorMotionAccumulator::finishSync() { + clearRelativeAxes(); +} + + +// --- CursorScrollAccumulator --- + +CursorScrollAccumulator::CursorScrollAccumulator() : + mHaveRelWheel(false), mHaveRelHWheel(false) { + clearRelativeAxes(); +} + +void CursorScrollAccumulator::configure(InputDevice* device) { + mHaveRelWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_WHEEL); + mHaveRelHWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_HWHEEL); +} + +void CursorScrollAccumulator::reset(InputDevice* device) { + clearRelativeAxes(); +} + +void CursorScrollAccumulator::clearRelativeAxes() { + mRelWheel = 0; + mRelHWheel = 0; +} + +void CursorScrollAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_REL) { + switch (rawEvent->code) { + case REL_WHEEL: + mRelWheel = rawEvent->value; + break; + case REL_HWHEEL: + mRelHWheel = rawEvent->value; + break; + } + } +} + +void CursorScrollAccumulator::finishSync() { + clearRelativeAxes(); +} + + +// --- TouchButtonAccumulator --- + +TouchButtonAccumulator::TouchButtonAccumulator() : + mHaveBtnTouch(false), mHaveStylus(false) { + clearButtons(); +} + +void TouchButtonAccumulator::configure(InputDevice* device) { + mHaveBtnTouch = device->hasKey(BTN_TOUCH); + mHaveStylus = device->hasKey(BTN_TOOL_PEN) + || device->hasKey(BTN_TOOL_RUBBER) + || device->hasKey(BTN_TOOL_BRUSH) + || device->hasKey(BTN_TOOL_PENCIL) + || device->hasKey(BTN_TOOL_AIRBRUSH); +} + +void TouchButtonAccumulator::reset(InputDevice* device) { + mBtnTouch = device->isKeyPressed(BTN_TOUCH); + mBtnStylus = device->isKeyPressed(BTN_STYLUS); + mBtnStylus2 = device->isKeyPressed(BTN_STYLUS); + mBtnToolFinger = device->isKeyPressed(BTN_TOOL_FINGER); + mBtnToolPen = device->isKeyPressed(BTN_TOOL_PEN); + mBtnToolRubber = device->isKeyPressed(BTN_TOOL_RUBBER); + mBtnToolBrush = device->isKeyPressed(BTN_TOOL_BRUSH); + mBtnToolPencil = device->isKeyPressed(BTN_TOOL_PENCIL); + mBtnToolAirbrush = device->isKeyPressed(BTN_TOOL_AIRBRUSH); + mBtnToolMouse = device->isKeyPressed(BTN_TOOL_MOUSE); + mBtnToolLens = device->isKeyPressed(BTN_TOOL_LENS); + mBtnToolDoubleTap = device->isKeyPressed(BTN_TOOL_DOUBLETAP); + mBtnToolTripleTap = device->isKeyPressed(BTN_TOOL_TRIPLETAP); + mBtnToolQuadTap = device->isKeyPressed(BTN_TOOL_QUADTAP); +} + +void TouchButtonAccumulator::clearButtons() { + mBtnTouch = 0; + mBtnStylus = 0; + mBtnStylus2 = 0; + mBtnToolFinger = 0; + mBtnToolPen = 0; + mBtnToolRubber = 0; + mBtnToolBrush = 0; + mBtnToolPencil = 0; + mBtnToolAirbrush = 0; + mBtnToolMouse = 0; + mBtnToolLens = 0; + mBtnToolDoubleTap = 0; + mBtnToolTripleTap = 0; + mBtnToolQuadTap = 0; +} + +void TouchButtonAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_KEY) { + switch (rawEvent->code) { + case BTN_TOUCH: + mBtnTouch = rawEvent->value; + break; + case BTN_STYLUS: + mBtnStylus = rawEvent->value; + break; + case BTN_STYLUS2: + mBtnStylus2 = rawEvent->value; + break; + case BTN_TOOL_FINGER: + mBtnToolFinger = rawEvent->value; + break; + case BTN_TOOL_PEN: + mBtnToolPen = rawEvent->value; + break; + case BTN_TOOL_RUBBER: + mBtnToolRubber = rawEvent->value; + break; + case BTN_TOOL_BRUSH: + mBtnToolBrush = rawEvent->value; + break; + case BTN_TOOL_PENCIL: + mBtnToolPencil = rawEvent->value; + break; + case BTN_TOOL_AIRBRUSH: + mBtnToolAirbrush = rawEvent->value; + break; + case BTN_TOOL_MOUSE: + mBtnToolMouse = rawEvent->value; + break; + case BTN_TOOL_LENS: + mBtnToolLens = rawEvent->value; + break; + case BTN_TOOL_DOUBLETAP: + mBtnToolDoubleTap = rawEvent->value; + break; + case BTN_TOOL_TRIPLETAP: + mBtnToolTripleTap = rawEvent->value; + break; + case BTN_TOOL_QUADTAP: + mBtnToolQuadTap = rawEvent->value; + break; + } + } +} + +uint32_t TouchButtonAccumulator::getButtonState() const { + uint32_t result = 0; + if (mBtnStylus) { + result |= AMOTION_EVENT_BUTTON_SECONDARY; + } + if (mBtnStylus2) { + result |= AMOTION_EVENT_BUTTON_TERTIARY; + } + return result; +} + +int32_t TouchButtonAccumulator::getToolType() const { + if (mBtnToolMouse || mBtnToolLens) { + return AMOTION_EVENT_TOOL_TYPE_MOUSE; + } + if (mBtnToolRubber) { + return AMOTION_EVENT_TOOL_TYPE_ERASER; + } + if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) { + return AMOTION_EVENT_TOOL_TYPE_STYLUS; + } + if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) { + return AMOTION_EVENT_TOOL_TYPE_FINGER; + } + return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; +} + +bool TouchButtonAccumulator::isToolActive() const { + return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber + || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush + || mBtnToolMouse || mBtnToolLens + || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap; +} + +bool TouchButtonAccumulator::isHovering() const { + return mHaveBtnTouch && !mBtnTouch; +} + +bool TouchButtonAccumulator::hasStylus() const { + return mHaveStylus; +} + + +// --- RawPointerAxes --- + +RawPointerAxes::RawPointerAxes() { + clear(); +} + +void RawPointerAxes::clear() { + x.clear(); + y.clear(); + pressure.clear(); + touchMajor.clear(); + touchMinor.clear(); + toolMajor.clear(); + toolMinor.clear(); + orientation.clear(); + distance.clear(); + tiltX.clear(); + tiltY.clear(); + trackingId.clear(); + slot.clear(); +} + + +// --- RawPointerData --- + +RawPointerData::RawPointerData() { + clear(); +} + +void RawPointerData::clear() { + pointerCount = 0; + clearIdBits(); +} + +void RawPointerData::copyFrom(const RawPointerData& other) { + pointerCount = other.pointerCount; + hoveringIdBits = other.hoveringIdBits; + touchingIdBits = other.touchingIdBits; + + for (uint32_t i = 0; i < pointerCount; i++) { + pointers[i] = other.pointers[i]; + + int id = pointers[i].id; + idToIndex[id] = other.idToIndex[id]; + } +} + +void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const { + float x = 0, y = 0; + uint32_t count = touchingIdBits.count(); + if (count) { + for (BitSet32 idBits(touchingIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + const Pointer& pointer = pointerForId(id); + x += pointer.x; + y += pointer.y; + } + x /= count; + y /= count; + } + *outX = x; + *outY = y; +} + + +// --- CookedPointerData --- + +CookedPointerData::CookedPointerData() { + clear(); +} + +void CookedPointerData::clear() { + pointerCount = 0; + hoveringIdBits.clear(); + touchingIdBits.clear(); +} + +void CookedPointerData::copyFrom(const CookedPointerData& other) { + pointerCount = other.pointerCount; + hoveringIdBits = other.hoveringIdBits; + touchingIdBits = other.touchingIdBits; + + for (uint32_t i = 0; i < pointerCount; i++) { + pointerProperties[i].copyFrom(other.pointerProperties[i]); + pointerCoords[i].copyFrom(other.pointerCoords[i]); + + int id = pointerProperties[i].id; + idToIndex[id] = other.idToIndex[id]; + } +} + + +// --- SingleTouchMotionAccumulator --- + +SingleTouchMotionAccumulator::SingleTouchMotionAccumulator() { + clearAbsoluteAxes(); +} + +void SingleTouchMotionAccumulator::reset(InputDevice* device) { + mAbsX = device->getAbsoluteAxisValue(ABS_X); + mAbsY = device->getAbsoluteAxisValue(ABS_Y); + mAbsPressure = device->getAbsoluteAxisValue(ABS_PRESSURE); + mAbsToolWidth = device->getAbsoluteAxisValue(ABS_TOOL_WIDTH); + mAbsDistance = device->getAbsoluteAxisValue(ABS_DISTANCE); + mAbsTiltX = device->getAbsoluteAxisValue(ABS_TILT_X); + mAbsTiltY = device->getAbsoluteAxisValue(ABS_TILT_Y); +} + +void SingleTouchMotionAccumulator::clearAbsoluteAxes() { + mAbsX = 0; + mAbsY = 0; + mAbsPressure = 0; + mAbsToolWidth = 0; + mAbsDistance = 0; + mAbsTiltX = 0; + mAbsTiltY = 0; +} + +void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_ABS) { + switch (rawEvent->code) { + case ABS_X: + mAbsX = rawEvent->value; + break; + case ABS_Y: + mAbsY = rawEvent->value; + break; + case ABS_PRESSURE: + mAbsPressure = rawEvent->value; + break; + case ABS_TOOL_WIDTH: + mAbsToolWidth = rawEvent->value; + break; + case ABS_DISTANCE: + mAbsDistance = rawEvent->value; + break; + case ABS_TILT_X: + mAbsTiltX = rawEvent->value; + break; + case ABS_TILT_Y: + mAbsTiltY = rawEvent->value; + break; + } + } +} + + +// --- MultiTouchMotionAccumulator --- + +MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() : + mCurrentSlot(-1), mSlots(NULL), mSlotCount(0), mUsingSlotsProtocol(false), + mHaveStylus(false) { +} + +MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() { + delete[] mSlots; +} + +void MultiTouchMotionAccumulator::configure(InputDevice* device, + size_t slotCount, bool usingSlotsProtocol) { + mSlotCount = slotCount; + mUsingSlotsProtocol = usingSlotsProtocol; + mHaveStylus = device->hasAbsoluteAxis(ABS_MT_TOOL_TYPE); + + delete[] mSlots; + mSlots = new Slot[slotCount]; +} + +void MultiTouchMotionAccumulator::reset(InputDevice* device) { + // Unfortunately there is no way to read the initial contents of the slots. + // So when we reset the accumulator, we must assume they are all zeroes. + if (mUsingSlotsProtocol) { + // Query the driver for the current slot index and use it as the initial slot + // before we start reading events from the device. It is possible that the + // current slot index will not be the same as it was when the first event was + // written into the evdev buffer, which means the input mapper could start + // out of sync with the initial state of the events in the evdev buffer. + // In the extremely unlikely case that this happens, the data from + // two slots will be confused until the next ABS_MT_SLOT event is received. + // This can cause the touch point to "jump", but at least there will be + // no stuck touches. + int32_t initialSlot; + status_t status = device->getEventHub()->getAbsoluteAxisValue(device->getId(), + ABS_MT_SLOT, &initialSlot); + if (status) { + ALOGD("Could not retrieve current multitouch slot index. status=%d", status); + initialSlot = -1; + } + clearSlots(initialSlot); + } else { + clearSlots(-1); + } +} + +void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { + if (mSlots) { + for (size_t i = 0; i < mSlotCount; i++) { + mSlots[i].clear(); + } + } + mCurrentSlot = initialSlot; +} + +void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_ABS) { + bool newSlot = false; + if (mUsingSlotsProtocol) { + if (rawEvent->code == ABS_MT_SLOT) { + mCurrentSlot = rawEvent->value; + newSlot = true; + } + } else if (mCurrentSlot < 0) { + mCurrentSlot = 0; + } + + if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) { +#if DEBUG_POINTERS + if (newSlot) { + ALOGW("MultiTouch device emitted invalid slot index %d but it " + "should be between 0 and %d; ignoring this slot.", + mCurrentSlot, mSlotCount - 1); + } +#endif + } else { + Slot* slot = &mSlots[mCurrentSlot]; + + switch (rawEvent->code) { + case ABS_MT_POSITION_X: + slot->mInUse = true; + slot->mAbsMTPositionX = rawEvent->value; + break; + case ABS_MT_POSITION_Y: + slot->mInUse = true; + slot->mAbsMTPositionY = rawEvent->value; + break; + case ABS_MT_TOUCH_MAJOR: + slot->mInUse = true; + slot->mAbsMTTouchMajor = rawEvent->value; + break; + case ABS_MT_TOUCH_MINOR: + slot->mInUse = true; + slot->mAbsMTTouchMinor = rawEvent->value; + slot->mHaveAbsMTTouchMinor = true; + break; + case ABS_MT_WIDTH_MAJOR: + slot->mInUse = true; + slot->mAbsMTWidthMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MINOR: + slot->mInUse = true; + slot->mAbsMTWidthMinor = rawEvent->value; + slot->mHaveAbsMTWidthMinor = true; + break; + case ABS_MT_ORIENTATION: + slot->mInUse = true; + slot->mAbsMTOrientation = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + if (mUsingSlotsProtocol && rawEvent->value < 0) { + // The slot is no longer in use but it retains its previous contents, + // which may be reused for subsequent touches. + slot->mInUse = false; + } else { + slot->mInUse = true; + slot->mAbsMTTrackingId = rawEvent->value; + } + break; + case ABS_MT_PRESSURE: + slot->mInUse = true; + slot->mAbsMTPressure = rawEvent->value; + break; + case ABS_MT_DISTANCE: + slot->mInUse = true; + slot->mAbsMTDistance = rawEvent->value; + break; + case ABS_MT_TOOL_TYPE: + slot->mInUse = true; + slot->mAbsMTToolType = rawEvent->value; + slot->mHaveAbsMTToolType = true; + break; + } + } + } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { + // MultiTouch Sync: The driver has returned all data for *one* of the pointers. + mCurrentSlot += 1; + } +} + +void MultiTouchMotionAccumulator::finishSync() { + if (!mUsingSlotsProtocol) { + clearSlots(-1); + } +} + +bool MultiTouchMotionAccumulator::hasStylus() const { + return mHaveStylus; +} + + +// --- MultiTouchMotionAccumulator::Slot --- + +MultiTouchMotionAccumulator::Slot::Slot() { + clear(); +} + +void MultiTouchMotionAccumulator::Slot::clear() { + mInUse = false; + mHaveAbsMTTouchMinor = false; + mHaveAbsMTWidthMinor = false; + mHaveAbsMTToolType = false; + mAbsMTPositionX = 0; + mAbsMTPositionY = 0; + mAbsMTTouchMajor = 0; + mAbsMTTouchMinor = 0; + mAbsMTWidthMajor = 0; + mAbsMTWidthMinor = 0; + mAbsMTOrientation = 0; + mAbsMTTrackingId = -1; + mAbsMTPressure = 0; + mAbsMTDistance = 0; + mAbsMTToolType = 0; +} + +int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { + if (mHaveAbsMTToolType) { + switch (mAbsMTToolType) { + case MT_TOOL_FINGER: + return AMOTION_EVENT_TOOL_TYPE_FINGER; + case MT_TOOL_PEN: + return AMOTION_EVENT_TOOL_TYPE_STYLUS; + } + } + return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; +} + + +// --- InputMapper --- + +InputMapper::InputMapper(InputDevice* device) : + mDevice(device), mContext(device->getContext()) { +} + +InputMapper::~InputMapper() { +} + +void InputMapper::populateDeviceInfo(InputDeviceInfo* info) { + info->addSource(getSources()); +} + +void InputMapper::dump(String8& dump) { +} + +void InputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, uint32_t changes) { +} + +void InputMapper::reset(nsecs_t when) { +} + +void InputMapper::timeoutExpired(nsecs_t when) { +} + +int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return AKEY_STATE_UNKNOWN; +} + +int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return AKEY_STATE_UNKNOWN; +} + +int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return AKEY_STATE_UNKNOWN; +} + +bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + return false; +} + +void InputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, + int32_t token) { +} + +void InputMapper::cancelVibrate(int32_t token) { +} + +int32_t InputMapper::getMetaState() { + return 0; +} + +void InputMapper::fadePointer() { +} + +status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) { + return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo); +} + +void InputMapper::bumpGeneration() { + mDevice->bumpGeneration(); +} + +void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump, + const RawAbsoluteAxisInfo& axis, const char* name) { + if (axis.valid) { + dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n", + name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution); + } else { + dump.appendFormat(INDENT4 "%s: unknown range\n", name); + } +} + + +// --- SwitchInputMapper --- + +SwitchInputMapper::SwitchInputMapper(InputDevice* device) : + InputMapper(device), mUpdatedSwitchValues(0), mUpdatedSwitchMask(0) { +} + +SwitchInputMapper::~SwitchInputMapper() { +} + +uint32_t SwitchInputMapper::getSources() { + return AINPUT_SOURCE_SWITCH; +} + +void SwitchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_SW: + processSwitch(rawEvent->code, rawEvent->value); + break; + + case EV_SYN: + if (rawEvent->code == SYN_REPORT) { + sync(rawEvent->when); + } + } +} + +void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) { + if (switchCode >= 0 && switchCode < 32) { + if (switchValue) { + mUpdatedSwitchValues |= 1 << switchCode; + } + mUpdatedSwitchMask |= 1 << switchCode; + } +} + +void SwitchInputMapper::sync(nsecs_t when) { + if (mUpdatedSwitchMask) { + NotifySwitchArgs args(when, 0, mUpdatedSwitchValues, mUpdatedSwitchMask); + getListener()->notifySwitch(&args); + + mUpdatedSwitchValues = 0; + mUpdatedSwitchMask = 0; + } +} + +int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return getEventHub()->getSwitchState(getDeviceId(), switchCode); +} + + +// --- VibratorInputMapper --- + +VibratorInputMapper::VibratorInputMapper(InputDevice* device) : + InputMapper(device), mVibrating(false) { +} + +VibratorInputMapper::~VibratorInputMapper() { +} + +uint32_t VibratorInputMapper::getSources() { + return 0; +} + +void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + info->setVibrator(true); +} + +void VibratorInputMapper::process(const RawEvent* rawEvent) { + // TODO: Handle FF_STATUS, although it does not seem to be widely supported. +} + +void VibratorInputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, + int32_t token) { +#if DEBUG_VIBRATOR + String8 patternStr; + for (size_t i = 0; i < patternSize; i++) { + if (i != 0) { + patternStr.append(", "); + } + patternStr.appendFormat("%lld", pattern[i]); + } + ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%ld, token=%d", + getDeviceId(), patternStr.string(), repeat, token); +#endif + + mVibrating = true; + memcpy(mPattern, pattern, patternSize * sizeof(nsecs_t)); + mPatternSize = patternSize; + mRepeat = repeat; + mToken = token; + mIndex = -1; + + nextStep(); +} + +void VibratorInputMapper::cancelVibrate(int32_t token) { +#if DEBUG_VIBRATOR + ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token); +#endif + + if (mVibrating && mToken == token) { + stopVibrating(); + } +} + +void VibratorInputMapper::timeoutExpired(nsecs_t when) { + if (mVibrating) { + if (when >= mNextStepTime) { + nextStep(); + } else { + getContext()->requestTimeoutAtTime(mNextStepTime); + } + } +} + +void VibratorInputMapper::nextStep() { + mIndex += 1; + if (size_t(mIndex) >= mPatternSize) { + if (mRepeat < 0) { + // We are done. + stopVibrating(); + return; + } + mIndex = mRepeat; + } + + bool vibratorOn = mIndex & 1; + nsecs_t duration = mPattern[mIndex]; + if (vibratorOn) { +#if DEBUG_VIBRATOR + ALOGD("nextStep: sending vibrate deviceId=%d, duration=%lld", + getDeviceId(), duration); +#endif + getEventHub()->vibrate(getDeviceId(), duration); + } else { +#if DEBUG_VIBRATOR + ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId()); +#endif + getEventHub()->cancelVibrate(getDeviceId()); + } + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + mNextStepTime = now + duration; + getContext()->requestTimeoutAtTime(mNextStepTime); +#if DEBUG_VIBRATOR + ALOGD("nextStep: scheduled timeout in %0.3fms", duration * 0.000001f); +#endif +} + +void VibratorInputMapper::stopVibrating() { + mVibrating = false; +#if DEBUG_VIBRATOR + ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId()); +#endif + getEventHub()->cancelVibrate(getDeviceId()); +} + +void VibratorInputMapper::dump(String8& dump) { + dump.append(INDENT2 "Vibrator Input Mapper:\n"); + dump.appendFormat(INDENT3 "Vibrating: %s\n", toString(mVibrating)); +} + + +// --- KeyboardInputMapper --- + +KeyboardInputMapper::KeyboardInputMapper(InputDevice* device, + uint32_t source, int32_t keyboardType) : + InputMapper(device), mSource(source), + mKeyboardType(keyboardType) { +} + +KeyboardInputMapper::~KeyboardInputMapper() { +} + +uint32_t KeyboardInputMapper::getSources() { + return mSource; +} + +void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + info->setKeyboardType(mKeyboardType); + info->setKeyCharacterMap(getEventHub()->getKeyCharacterMap(getDeviceId())); +} + +void KeyboardInputMapper::dump(String8& dump) { + dump.append(INDENT2 "Keyboard Input Mapper:\n"); + dumpParameters(dump); + dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType); + dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation); + dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mKeyDowns.size()); + dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mMetaState); + dump.appendFormat(INDENT3 "DownTime: %lld\n", mDownTime); +} + + +void KeyboardInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, uint32_t changes) { + InputMapper::configure(when, config, changes); + + if (!changes) { // first time only + // Configure basic parameters. + configureParameters(); + } + + if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) { + DisplayViewport v; + if (config->getDisplayInfo(false /*external*/, &v)) { + mOrientation = v.orientation; + } else { + mOrientation = DISPLAY_ORIENTATION_0; + } + } else { + mOrientation = DISPLAY_ORIENTATION_0; + } + } +} + +void KeyboardInputMapper::configureParameters() { + mParameters.orientationAware = false; + getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"), + mParameters.orientationAware); + + mParameters.hasAssociatedDisplay = false; + if (mParameters.orientationAware) { + mParameters.hasAssociatedDisplay = true; + } +} + +void KeyboardInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n", + toString(mParameters.hasAssociatedDisplay)); + dump.appendFormat(INDENT4 "OrientationAware: %s\n", + toString(mParameters.orientationAware)); +} + +void KeyboardInputMapper::reset(nsecs_t when) { + mMetaState = AMETA_NONE; + mDownTime = 0; + mKeyDowns.clear(); + mCurrentHidUsage = 0; + + resetLedState(); + + InputMapper::reset(when); +} + +void KeyboardInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: { + int32_t scanCode = rawEvent->code; + int32_t usageCode = mCurrentHidUsage; + mCurrentHidUsage = 0; + + if (isKeyboardOrGamepadKey(scanCode)) { + int32_t keyCode; + uint32_t flags; + if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) { + keyCode = AKEYCODE_UNKNOWN; + flags = 0; + } + processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags); + } + break; + } + case EV_MSC: { + if (rawEvent->code == MSC_SCAN) { + mCurrentHidUsage = rawEvent->value; + } + break; + } + case EV_SYN: { + if (rawEvent->code == SYN_REPORT) { + mCurrentHidUsage = 0; + } + } + } +} + +bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) { + return scanCode < BTN_MOUSE + || scanCode >= KEY_OK + || (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) + || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI); +} + +void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, + int32_t scanCode, uint32_t policyFlags) { + + if (down) { + // Rotate key codes according to orientation if needed. + if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) { + keyCode = rotateKeyCode(keyCode, mOrientation); + } + + // Add key down. + ssize_t keyDownIndex = findKeyDown(scanCode); + if (keyDownIndex >= 0) { + // key repeat, be sure to use same keycode as before in case of rotation + keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode; + } else { + // key down + if ((policyFlags & POLICY_FLAG_VIRTUAL) + && mContext->shouldDropVirtualKey(when, + getDevice(), keyCode, scanCode)) { + return; + } + + mKeyDowns.push(); + KeyDown& keyDown = mKeyDowns.editTop(); + keyDown.keyCode = keyCode; + keyDown.scanCode = scanCode; + } + + mDownTime = when; + } else { + // Remove key down. + ssize_t keyDownIndex = findKeyDown(scanCode); + if (keyDownIndex >= 0) { + // key up, be sure to use same keycode as before in case of rotation + keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode; + mKeyDowns.removeAt(size_t(keyDownIndex)); + } else { + // key was not actually down + ALOGI("Dropping key up from device %s because the key was not down. " + "keyCode=%d, scanCode=%d", + getDeviceName().string(), keyCode, scanCode); + return; + } + } + + int32_t oldMetaState = mMetaState; + int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState); + bool metaStateChanged = oldMetaState != newMetaState; + if (metaStateChanged) { + mMetaState = newMetaState; + updateLedState(false); + } + + nsecs_t downTime = mDownTime; + + // Key down on external an keyboard should wake the device. + // We don't do this for internal keyboards to prevent them from waking up in your pocket. + // For internal keyboards, the key layout file should specify the policy flags for + // each wake key individually. + // TODO: Use the input device configuration to control this behavior more finely. + if (down && getDevice()->isExternal() + && !(policyFlags & (POLICY_FLAG_WAKE | POLICY_FLAG_WAKE_DROPPED))) { + policyFlags |= POLICY_FLAG_WAKE_DROPPED; + } + + if (metaStateChanged) { + getContext()->updateGlobalMetaState(); + } + + if (down && !isMetaKey(keyCode)) { + getContext()->fadePointer(); + } + + NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags, + down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime); + getListener()->notifyKey(&args); +} + +ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) { + size_t n = mKeyDowns.size(); + for (size_t i = 0; i < n; i++) { + if (mKeyDowns[i].scanCode == scanCode) { + return i; + } + } + return -1; +} + +int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return getEventHub()->getKeyCodeState(getDeviceId(), keyCode); +} + +int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return getEventHub()->getScanCodeState(getDeviceId(), scanCode); +} + +bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags); +} + +int32_t KeyboardInputMapper::getMetaState() { + return mMetaState; +} + +void KeyboardInputMapper::resetLedState() { + initializeLedState(mCapsLockLedState, ALED_CAPS_LOCK); + initializeLedState(mNumLockLedState, ALED_NUM_LOCK); + initializeLedState(mScrollLockLedState, ALED_SCROLL_LOCK); + + updateLedState(true); +} + +void KeyboardInputMapper::initializeLedState(LedState& ledState, int32_t led) { + ledState.avail = getEventHub()->hasLed(getDeviceId(), led); + ledState.on = false; +} + +void KeyboardInputMapper::updateLedState(bool reset) { + updateLedStateForModifier(mCapsLockLedState, ALED_CAPS_LOCK, + AMETA_CAPS_LOCK_ON, reset); + updateLedStateForModifier(mNumLockLedState, ALED_NUM_LOCK, + AMETA_NUM_LOCK_ON, reset); + updateLedStateForModifier(mScrollLockLedState, ALED_SCROLL_LOCK, + AMETA_SCROLL_LOCK_ON, reset); +} + +void KeyboardInputMapper::updateLedStateForModifier(LedState& ledState, + int32_t led, int32_t modifier, bool reset) { + if (ledState.avail) { + bool desiredState = (mMetaState & modifier) != 0; + if (reset || ledState.on != desiredState) { + getEventHub()->setLedState(getDeviceId(), led, desiredState); + ledState.on = desiredState; + } + } +} + + +// --- CursorInputMapper --- + +CursorInputMapper::CursorInputMapper(InputDevice* device) : + InputMapper(device) { +} + +CursorInputMapper::~CursorInputMapper() { +} + +uint32_t CursorInputMapper::getSources() { + return mSource; +} + +void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + if (mParameters.mode == Parameters::MODE_POINTER) { + float minX, minY, maxX, maxY; + if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f); + } + } else { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f); + } + info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); + + if (mCursorScrollAccumulator.haveRelativeVWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + } + if (mCursorScrollAccumulator.haveRelativeHWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + } +} + +void CursorInputMapper::dump(String8& dump) { + dump.append(INDENT2 "Cursor Input Mapper:\n"); + dumpParameters(dump); + dump.appendFormat(INDENT3 "XScale: %0.3f\n", mXScale); + dump.appendFormat(INDENT3 "YScale: %0.3f\n", mYScale); + dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision); + dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision); + dump.appendFormat(INDENT3 "HaveVWheel: %s\n", + toString(mCursorScrollAccumulator.haveRelativeVWheel())); + dump.appendFormat(INDENT3 "HaveHWheel: %s\n", + toString(mCursorScrollAccumulator.haveRelativeHWheel())); + dump.appendFormat(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale); + dump.appendFormat(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale); + dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation); + dump.appendFormat(INDENT3 "ButtonState: 0x%08x\n", mButtonState); + dump.appendFormat(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState))); + dump.appendFormat(INDENT3 "DownTime: %lld\n", mDownTime); +} + +void CursorInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, uint32_t changes) { + InputMapper::configure(when, config, changes); + + if (!changes) { // first time only + mCursorScrollAccumulator.configure(getDevice()); + + // Configure basic parameters. + configureParameters(); + + // Configure device mode. + switch (mParameters.mode) { + case Parameters::MODE_POINTER: + mSource = AINPUT_SOURCE_MOUSE; + mXPrecision = 1.0f; + mYPrecision = 1.0f; + mXScale = 1.0f; + mYScale = 1.0f; + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + break; + case Parameters::MODE_NAVIGATION: + mSource = AINPUT_SOURCE_TRACKBALL; + mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + break; + } + + mVWheelScale = 1.0f; + mHWheelScale = 1.0f; + } + + if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) { + mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters); + mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters); + mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters); + } + + if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) { + DisplayViewport v; + if (config->getDisplayInfo(false /*external*/, &v)) { + mOrientation = v.orientation; + } else { + mOrientation = DISPLAY_ORIENTATION_0; + } + } else { + mOrientation = DISPLAY_ORIENTATION_0; + } + bumpGeneration(); + } +} + +void CursorInputMapper::configureParameters() { + mParameters.mode = Parameters::MODE_POINTER; + String8 cursorModeString; + if (getDevice()->getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) { + if (cursorModeString == "navigation") { + mParameters.mode = Parameters::MODE_NAVIGATION; + } else if (cursorModeString != "pointer" && cursorModeString != "default") { + ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string()); + } + } + + mParameters.orientationAware = false; + getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"), + mParameters.orientationAware); + + mParameters.hasAssociatedDisplay = false; + if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) { + mParameters.hasAssociatedDisplay = true; + } +} + +void CursorInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n", + toString(mParameters.hasAssociatedDisplay)); + + 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: + ALOG_ASSERT(false); + } + + dump.appendFormat(INDENT4 "OrientationAware: %s\n", + toString(mParameters.orientationAware)); +} + +void CursorInputMapper::reset(nsecs_t when) { + mButtonState = 0; + mDownTime = 0; + + mPointerVelocityControl.reset(); + mWheelXVelocityControl.reset(); + mWheelYVelocityControl.reset(); + + mCursorButtonAccumulator.reset(getDevice()); + mCursorMotionAccumulator.reset(getDevice()); + mCursorScrollAccumulator.reset(getDevice()); + + InputMapper::reset(when); +} + +void CursorInputMapper::process(const RawEvent* rawEvent) { + mCursorButtonAccumulator.process(rawEvent); + mCursorMotionAccumulator.process(rawEvent); + mCursorScrollAccumulator.process(rawEvent); + + if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + sync(rawEvent->when); + } +} + +void CursorInputMapper::sync(nsecs_t when) { + int32_t lastButtonState = mButtonState; + int32_t currentButtonState = mCursorButtonAccumulator.getButtonState(); + mButtonState = currentButtonState; + + bool wasDown = isPointerDown(lastButtonState); + bool down = isPointerDown(currentButtonState); + bool downChanged; + if (!wasDown && down) { + mDownTime = when; + downChanged = true; + } else if (wasDown && !down) { + downChanged = true; + } else { + downChanged = false; + } + nsecs_t downTime = mDownTime; + bool buttonsChanged = currentButtonState != lastButtonState; + bool buttonsPressed = currentButtonState & ~lastButtonState; + + float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale; + float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale; + bool moved = deltaX != 0 || deltaY != 0; + + // Rotate delta according to orientation if needed. + if (mParameters.orientationAware && mParameters.hasAssociatedDisplay + && (deltaX != 0.0f || deltaY != 0.0f)) { + rotateDelta(mOrientation, &deltaX, &deltaY); + } + + // Move the pointer. + PointerProperties pointerProperties; + pointerProperties.clear(); + pointerProperties.id = 0; + pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE; + + PointerCoords pointerCoords; + pointerCoords.clear(); + + float vscroll = mCursorScrollAccumulator.getRelativeVWheel(); + float hscroll = mCursorScrollAccumulator.getRelativeHWheel(); + bool scrolled = vscroll != 0 || hscroll != 0; + + mWheelYVelocityControl.move(when, NULL, &vscroll); + mWheelXVelocityControl.move(when, &hscroll, NULL); + + mPointerVelocityControl.move(when, &deltaX, &deltaY); + + int32_t displayId; + if (mPointerController != NULL) { + if (moved || scrolled || buttonsChanged) { + mPointerController->setPresentation( + PointerControllerInterface::PRESENTATION_POINTER); + + if (moved) { + mPointerController->move(deltaX, deltaY); + } + + if (buttonsChanged) { + mPointerController->setButtonState(currentButtonState); + } + + mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE); + } + + float x, y; + mPointerController->getPosition(&x, &y); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + displayId = ADISPLAY_ID_DEFAULT; + } else { + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY); + displayId = ADISPLAY_ID_NONE; + } + + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); + + // Moving an external trackball or mouse should wake the device. + // We don't do this for internal cursor devices to prevent them from waking up + // the device in your pocket. + // TODO: Use the input device configuration to control this behavior more finely. + uint32_t policyFlags = 0; + if ((buttonsPressed || moved || scrolled) && getDevice()->isExternal()) { + policyFlags |= POLICY_FLAG_WAKE_DROPPED; + } + + // Synthesize key down from buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource, + policyFlags, lastButtonState, currentButtonState); + + // Send motion event. + if (downChanged || moved || scrolled || buttonsChanged) { + int32_t metaState = mContext->getGlobalMetaState(); + int32_t motionEventAction; + if (downChanged) { + motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; + } else if (down || mPointerController == NULL) { + motionEventAction = AMOTION_EVENT_ACTION_MOVE; + } else { + motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE; + } + + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + motionEventAction, 0, metaState, currentButtonState, 0, + displayId, 1, &pointerProperties, &pointerCoords, + mXPrecision, mYPrecision, downTime); + getListener()->notifyMotion(&args); + + // Send hover move after UP to tell the application that the mouse is hovering now. + if (motionEventAction == AMOTION_EVENT_ACTION_UP + && mPointerController != NULL) { + NotifyMotionArgs hoverArgs(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, + metaState, currentButtonState, AMOTION_EVENT_EDGE_FLAG_NONE, + displayId, 1, &pointerProperties, &pointerCoords, + mXPrecision, mYPrecision, downTime); + getListener()->notifyMotion(&hoverArgs); + } + + // Send scroll events. + if (scrolled) { + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); + + NotifyMotionArgs scrollArgs(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_SCROLL, 0, metaState, currentButtonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + displayId, 1, &pointerProperties, &pointerCoords, + mXPrecision, mYPrecision, downTime); + getListener()->notifyMotion(&scrollArgs); + } + } + + // Synthesize key up from buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource, + policyFlags, lastButtonState, currentButtonState); + + mCursorMotionAccumulator.finishSync(); + mCursorScrollAccumulator.finishSync(); +} + +int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) { + return getEventHub()->getScanCodeState(getDeviceId(), scanCode); + } else { + return AKEY_STATE_UNKNOWN; + } +} + +void CursorInputMapper::fadePointer() { + if (mPointerController != NULL) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + } +} + + +// --- TouchInputMapper --- + +TouchInputMapper::TouchInputMapper(InputDevice* device) : + InputMapper(device), + mSource(0), mDeviceMode(DEVICE_MODE_DISABLED), + mSurfaceWidth(-1), mSurfaceHeight(-1), mSurfaceLeft(0), mSurfaceTop(0), + mSurfaceOrientation(DISPLAY_ORIENTATION_0) { +} + +TouchInputMapper::~TouchInputMapper() { +} + +uint32_t TouchInputMapper::getSources() { + return mSource; +} + +void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + if (mDeviceMode != DEVICE_MODE_DISABLED) { + info->addMotionRange(mOrientedRanges.x); + info->addMotionRange(mOrientedRanges.y); + info->addMotionRange(mOrientedRanges.pressure); + + if (mOrientedRanges.haveSize) { + info->addMotionRange(mOrientedRanges.size); + } + + if (mOrientedRanges.haveTouchSize) { + info->addMotionRange(mOrientedRanges.touchMajor); + info->addMotionRange(mOrientedRanges.touchMinor); + } + + if (mOrientedRanges.haveToolSize) { + info->addMotionRange(mOrientedRanges.toolMajor); + info->addMotionRange(mOrientedRanges.toolMinor); + } + + if (mOrientedRanges.haveOrientation) { + info->addMotionRange(mOrientedRanges.orientation); + } + + if (mOrientedRanges.haveDistance) { + info->addMotionRange(mOrientedRanges.distance); + } + + if (mOrientedRanges.haveTilt) { + info->addMotionRange(mOrientedRanges.tilt); + } + + if (mCursorScrollAccumulator.haveRelativeVWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, + 0.0f); + } + if (mCursorScrollAccumulator.haveRelativeHWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, + 0.0f); + } + if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) { + const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; + const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, + x.fuzz, x.resolution); + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, + y.fuzz, y.resolution); + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, + x.fuzz, x.resolution); + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, + y.fuzz, y.resolution); + } + info->setButtonUnderPad(mParameters.hasButtonUnderPad); + } +} + +void TouchInputMapper::dump(String8& dump) { + dump.append(INDENT2 "Touch Input Mapper:\n"); + dumpParameters(dump); + dumpVirtualKeys(dump); + dumpRawPointerAxes(dump); + dumpCalibration(dump); + dumpSurface(dump); + + dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n"); + dump.appendFormat(INDENT4 "XTranslate: %0.3f\n", mXTranslate); + dump.appendFormat(INDENT4 "YTranslate: %0.3f\n", mYTranslate); + dump.appendFormat(INDENT4 "XScale: %0.3f\n", mXScale); + dump.appendFormat(INDENT4 "YScale: %0.3f\n", mYScale); + dump.appendFormat(INDENT4 "XPrecision: %0.3f\n", mXPrecision); + dump.appendFormat(INDENT4 "YPrecision: %0.3f\n", mYPrecision); + dump.appendFormat(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale); + dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mPressureScale); + dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mSizeScale); + dump.appendFormat(INDENT4 "OrientationScale: %0.3f\n", mOrientationScale); + dump.appendFormat(INDENT4 "DistanceScale: %0.3f\n", mDistanceScale); + dump.appendFormat(INDENT4 "HaveTilt: %s\n", toString(mHaveTilt)); + dump.appendFormat(INDENT4 "TiltXCenter: %0.3f\n", mTiltXCenter); + dump.appendFormat(INDENT4 "TiltXScale: %0.3f\n", mTiltXScale); + dump.appendFormat(INDENT4 "TiltYCenter: %0.3f\n", mTiltYCenter); + dump.appendFormat(INDENT4 "TiltYScale: %0.3f\n", mTiltYScale); + + dump.appendFormat(INDENT3 "Last Button State: 0x%08x\n", mLastButtonState); + + dump.appendFormat(INDENT3 "Last Raw Touch: pointerCount=%d\n", + mLastRawPointerData.pointerCount); + for (uint32_t i = 0; i < mLastRawPointerData.pointerCount; i++) { + const RawPointerData::Pointer& pointer = mLastRawPointerData.pointers[i]; + dump.appendFormat(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, " + "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, " + "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, " + "toolType=%d, isHovering=%s\n", i, + pointer.id, pointer.x, pointer.y, pointer.pressure, + pointer.touchMajor, pointer.touchMinor, + pointer.toolMajor, pointer.toolMinor, + pointer.orientation, pointer.tiltX, pointer.tiltY, pointer.distance, + pointer.toolType, toString(pointer.isHovering)); + } + + dump.appendFormat(INDENT3 "Last Cooked Touch: pointerCount=%d\n", + mLastCookedPointerData.pointerCount); + for (uint32_t i = 0; i < mLastCookedPointerData.pointerCount; i++) { + const PointerProperties& pointerProperties = mLastCookedPointerData.pointerProperties[i]; + const PointerCoords& pointerCoords = mLastCookedPointerData.pointerCoords[i]; + dump.appendFormat(INDENT4 "[%d]: id=%d, x=%0.3f, y=%0.3f, pressure=%0.3f, " + "touchMajor=%0.3f, touchMinor=%0.3f, toolMajor=%0.3f, toolMinor=%0.3f, " + "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, " + "toolType=%d, isHovering=%s\n", i, + pointerProperties.id, + pointerCoords.getX(), + pointerCoords.getY(), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT), + pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), + pointerProperties.toolType, + toString(mLastCookedPointerData.isHovering(i))); + } + + if (mDeviceMode == DEVICE_MODE_POINTER) { + dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n"); + dump.appendFormat(INDENT4 "XMovementScale: %0.3f\n", + mPointerXMovementScale); + dump.appendFormat(INDENT4 "YMovementScale: %0.3f\n", + mPointerYMovementScale); + dump.appendFormat(INDENT4 "XZoomScale: %0.3f\n", + mPointerXZoomScale); + dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n", + mPointerYZoomScale); + dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n", + mPointerGestureMaxSwipeWidth); + } +} + +void TouchInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, uint32_t changes) { + InputMapper::configure(when, config, changes); + + mConfig = *config; + + if (!changes) { // first time only + // Configure basic parameters. + configureParameters(); + + // Configure common accumulators. + mCursorScrollAccumulator.configure(getDevice()); + mTouchButtonAccumulator.configure(getDevice()); + + // Configure absolute axis information. + configureRawPointerAxes(); + + // Prepare input device calibration. + parseCalibration(); + resolveCalibration(); + } + + if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) { + // Update pointer speed. + mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters); + mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters); + mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters); + } + + bool resetNeeded = false; + if (!changes || (changes & (InputReaderConfiguration::CHANGE_DISPLAY_INFO + | InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT + | InputReaderConfiguration::CHANGE_SHOW_TOUCHES))) { + // Configure device sources, surface dimensions, orientation and + // scaling factors. + configureSurface(when, &resetNeeded); + } + + if (changes && resetNeeded) { + // Send reset, unless this is the first time the device has been configured, + // in which case the reader will call reset itself after all mappers are ready. + getDevice()->notifyReset(when); + } +} + +void TouchInputMapper::configureParameters() { + // Use the pointer presentation mode for devices that do not support distinct + // multitouch. The spot-based presentation relies on being able to accurately + // locate two or more fingers on the touch pad. + mParameters.gestureMode = getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_SEMI_MT) + ? Parameters::GESTURE_MODE_POINTER : Parameters::GESTURE_MODE_SPOTS; + + String8 gestureModeString; + if (getDevice()->getConfiguration().tryGetProperty(String8("touch.gestureMode"), + gestureModeString)) { + if (gestureModeString == "pointer") { + mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER; + } else if (gestureModeString == "spots") { + mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS; + } else if (gestureModeString != "default") { + ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string()); + } + } + + if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) { + // The device is a touch screen. + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN; + } else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_POINTER)) { + // The device is a pointing device like a track pad. + mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER; + } else if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X) + || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) { + // The device is a cursor device with a touch pad attached. + // By default don't use the touch pad to move the pointer. + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + } else { + // The device is a touch pad of unknown purpose. + mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER; + } + + mParameters.hasButtonUnderPad= + getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_BUTTONPAD); + + String8 deviceTypeString; + if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"), + deviceTypeString)) { + if (deviceTypeString == "touchScreen") { + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN; + } else if (deviceTypeString == "touchPad") { + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + } else if (deviceTypeString == "touchNavigation") { + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION; + } else if (deviceTypeString == "pointer") { + mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER; + } else if (deviceTypeString != "default") { + ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string()); + } + } + + mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN; + getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"), + mParameters.orientationAware); + + mParameters.hasAssociatedDisplay = false; + mParameters.associatedDisplayIsExternal = false; + if (mParameters.orientationAware + || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + mParameters.hasAssociatedDisplay = true; + mParameters.associatedDisplayIsExternal = + mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + && getDevice()->isExternal(); + } +} + +void TouchInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + + switch (mParameters.gestureMode) { + case Parameters::GESTURE_MODE_POINTER: + dump.append(INDENT4 "GestureMode: pointer\n"); + break; + case Parameters::GESTURE_MODE_SPOTS: + dump.append(INDENT4 "GestureMode: spots\n"); + break; + default: + assert(false); + } + + 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; + case Parameters::DEVICE_TYPE_TOUCH_NAVIGATION: + dump.append(INDENT4 "DeviceType: touchNavigation\n"); + break; + case Parameters::DEVICE_TYPE_POINTER: + dump.append(INDENT4 "DeviceType: pointer\n"); + break; + default: + ALOG_ASSERT(false); + } + + dump.appendFormat(INDENT4 "AssociatedDisplay: hasAssociatedDisplay=%s, isExternal=%s\n", + toString(mParameters.hasAssociatedDisplay), + toString(mParameters.associatedDisplayIsExternal)); + dump.appendFormat(INDENT4 "OrientationAware: %s\n", + toString(mParameters.orientationAware)); +} + +void TouchInputMapper::configureRawPointerAxes() { + mRawPointerAxes.clear(); +} + +void TouchInputMapper::dumpRawPointerAxes(String8& dump) { + dump.append(INDENT3 "Raw Touch Axes:\n"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.x, "X"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.y, "Y"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.pressure, "Pressure"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMajor, "TouchMajor"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMinor, "TouchMinor"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMajor, "ToolMajor"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMinor, "ToolMinor"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.orientation, "Orientation"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.distance, "Distance"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltX, "TiltX"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltY, "TiltY"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.trackingId, "TrackingId"); + dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.slot, "Slot"); +} + +void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) { + int32_t oldDeviceMode = mDeviceMode; + + // Determine device mode. + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER + && mConfig.pointerGesturesEnabled) { + mSource = AINPUT_SOURCE_MOUSE; + mDeviceMode = DEVICE_MODE_POINTER; + if (hasStylus()) { + mSource |= AINPUT_SOURCE_STYLUS; + } + } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + && mParameters.hasAssociatedDisplay) { + mSource = AINPUT_SOURCE_TOUCHSCREEN; + mDeviceMode = DEVICE_MODE_DIRECT; + if (hasStylus()) { + mSource |= AINPUT_SOURCE_STYLUS; + } + } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) { + mSource = AINPUT_SOURCE_TOUCH_NAVIGATION; + mDeviceMode = DEVICE_MODE_NAVIGATION; + } else { + mSource = AINPUT_SOURCE_TOUCHPAD; + mDeviceMode = DEVICE_MODE_UNSCALED; + } + + // Ensure we have valid X and Y axes. + if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) { + ALOGW(INDENT "Touch device '%s' did not report support for X or Y axis! " + "The device will be inoperable.", getDeviceName().string()); + mDeviceMode = DEVICE_MODE_DISABLED; + return; + } + + // Raw width and height in the natural orientation. + int32_t rawWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1; + int32_t rawHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1; + + // Get associated display dimensions. + DisplayViewport newViewport; + if (mParameters.hasAssociatedDisplay) { + if (!mConfig.getDisplayInfo(mParameters.associatedDisplayIsExternal, &newViewport)) { + ALOGI(INDENT "Touch device '%s' could not query the properties of its associated " + "display. The device will be inoperable until the display size " + "becomes available.", + getDeviceName().string()); + mDeviceMode = DEVICE_MODE_DISABLED; + return; + } + } else { + newViewport.setNonDisplayViewport(rawWidth, rawHeight); + } + bool viewportChanged = mViewport != newViewport; + if (viewportChanged) { + mViewport = newViewport; + + if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) { + // Convert rotated viewport to natural surface coordinates. + int32_t naturalLogicalWidth, naturalLogicalHeight; + int32_t naturalPhysicalWidth, naturalPhysicalHeight; + int32_t naturalPhysicalLeft, naturalPhysicalTop; + int32_t naturalDeviceWidth, naturalDeviceHeight; + switch (mViewport.orientation) { + case DISPLAY_ORIENTATION_90: + naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop; + naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft; + naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop; + naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft; + naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom; + naturalPhysicalTop = mViewport.physicalLeft; + naturalDeviceWidth = mViewport.deviceHeight; + naturalDeviceHeight = mViewport.deviceWidth; + break; + case DISPLAY_ORIENTATION_180: + naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft; + naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop; + naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft; + naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop; + naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight; + naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom; + naturalDeviceWidth = mViewport.deviceWidth; + naturalDeviceHeight = mViewport.deviceHeight; + break; + case DISPLAY_ORIENTATION_270: + naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop; + naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft; + naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop; + naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft; + naturalPhysicalLeft = mViewport.physicalTop; + naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight; + naturalDeviceWidth = mViewport.deviceHeight; + naturalDeviceHeight = mViewport.deviceWidth; + break; + case DISPLAY_ORIENTATION_0: + default: + naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft; + naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop; + naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft; + naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop; + naturalPhysicalLeft = mViewport.physicalLeft; + naturalPhysicalTop = mViewport.physicalTop; + naturalDeviceWidth = mViewport.deviceWidth; + naturalDeviceHeight = mViewport.deviceHeight; + break; + } + + mSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth; + mSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight; + mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth; + mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight; + + mSurfaceOrientation = mParameters.orientationAware ? + mViewport.orientation : DISPLAY_ORIENTATION_0; + } else { + mSurfaceWidth = rawWidth; + mSurfaceHeight = rawHeight; + mSurfaceLeft = 0; + mSurfaceTop = 0; + mSurfaceOrientation = DISPLAY_ORIENTATION_0; + } + } + + // If moving between pointer modes, need to reset some state. + bool deviceModeChanged = mDeviceMode != oldDeviceMode; + if (deviceModeChanged) { + mOrientedRanges.clear(); + } + + // Create pointer controller if needed. + if (mDeviceMode == DEVICE_MODE_POINTER || + (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) { + if (mPointerController == NULL) { + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + } + } else { + mPointerController.clear(); + } + + if (viewportChanged || deviceModeChanged) { + ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, " + "display id %d", + getDeviceId(), getDeviceName().string(), mSurfaceWidth, mSurfaceHeight, + mSurfaceOrientation, mDeviceMode, mViewport.displayId); + + // Configure X and Y factors. + mXScale = float(mSurfaceWidth) / rawWidth; + mYScale = float(mSurfaceHeight) / rawHeight; + mXTranslate = -mSurfaceLeft; + mYTranslate = -mSurfaceTop; + mXPrecision = 1.0f / mXScale; + mYPrecision = 1.0f / mYScale; + + mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X; + mOrientedRanges.x.source = mSource; + mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y; + mOrientedRanges.y.source = mSource; + + configureVirtualKeys(); + + // Scale factor for terms that are not oriented in a particular axis. + // If the pixels are square then xScale == yScale otherwise we fake it + // by choosing an average. + mGeometricScale = avg(mXScale, mYScale); + + // Size of diagonal axis. + float diagonalSize = hypotf(mSurfaceWidth, mSurfaceHeight); + + // Size factors. + if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) { + if (mRawPointerAxes.touchMajor.valid + && mRawPointerAxes.touchMajor.maxValue != 0) { + mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue; + } else if (mRawPointerAxes.toolMajor.valid + && mRawPointerAxes.toolMajor.maxValue != 0) { + mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue; + } else { + mSizeScale = 0.0f; + } + + mOrientedRanges.haveTouchSize = true; + mOrientedRanges.haveToolSize = true; + mOrientedRanges.haveSize = true; + + mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR; + mOrientedRanges.touchMajor.source = mSource; + mOrientedRanges.touchMajor.min = 0; + mOrientedRanges.touchMajor.max = diagonalSize; + mOrientedRanges.touchMajor.flat = 0; + mOrientedRanges.touchMajor.fuzz = 0; + mOrientedRanges.touchMajor.resolution = 0; + + mOrientedRanges.touchMinor = mOrientedRanges.touchMajor; + mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR; + + mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR; + mOrientedRanges.toolMajor.source = mSource; + mOrientedRanges.toolMajor.min = 0; + mOrientedRanges.toolMajor.max = diagonalSize; + mOrientedRanges.toolMajor.flat = 0; + mOrientedRanges.toolMajor.fuzz = 0; + mOrientedRanges.toolMajor.resolution = 0; + + mOrientedRanges.toolMinor = mOrientedRanges.toolMajor; + mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR; + + mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE; + mOrientedRanges.size.source = mSource; + mOrientedRanges.size.min = 0; + mOrientedRanges.size.max = 1.0; + mOrientedRanges.size.flat = 0; + mOrientedRanges.size.fuzz = 0; + mOrientedRanges.size.resolution = 0; + } else { + mSizeScale = 0.0f; + } + + // Pressure factors. + mPressureScale = 0; + if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL + || mCalibration.pressureCalibration + == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) { + if (mCalibration.havePressureScale) { + mPressureScale = mCalibration.pressureScale; + } else if (mRawPointerAxes.pressure.valid + && mRawPointerAxes.pressure.maxValue != 0) { + mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue; + } + } + + mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE; + mOrientedRanges.pressure.source = mSource; + mOrientedRanges.pressure.min = 0; + mOrientedRanges.pressure.max = 1.0; + mOrientedRanges.pressure.flat = 0; + mOrientedRanges.pressure.fuzz = 0; + mOrientedRanges.pressure.resolution = 0; + + // Tilt + mTiltXCenter = 0; + mTiltXScale = 0; + mTiltYCenter = 0; + mTiltYScale = 0; + mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid; + if (mHaveTilt) { + mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue, + mRawPointerAxes.tiltX.maxValue); + mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue, + mRawPointerAxes.tiltY.maxValue); + mTiltXScale = M_PI / 180; + mTiltYScale = M_PI / 180; + + mOrientedRanges.haveTilt = true; + + mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT; + mOrientedRanges.tilt.source = mSource; + mOrientedRanges.tilt.min = 0; + mOrientedRanges.tilt.max = M_PI_2; + mOrientedRanges.tilt.flat = 0; + mOrientedRanges.tilt.fuzz = 0; + mOrientedRanges.tilt.resolution = 0; + } + + // Orientation + mOrientationScale = 0; + if (mHaveTilt) { + mOrientedRanges.haveOrientation = true; + + mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION; + mOrientedRanges.orientation.source = mSource; + mOrientedRanges.orientation.min = -M_PI; + mOrientedRanges.orientation.max = M_PI; + mOrientedRanges.orientation.flat = 0; + mOrientedRanges.orientation.fuzz = 0; + mOrientedRanges.orientation.resolution = 0; + } else if (mCalibration.orientationCalibration != + Calibration::ORIENTATION_CALIBRATION_NONE) { + if (mCalibration.orientationCalibration + == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) { + if (mRawPointerAxes.orientation.valid) { + if (mRawPointerAxes.orientation.maxValue > 0) { + mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue; + } else if (mRawPointerAxes.orientation.minValue < 0) { + mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue; + } else { + mOrientationScale = 0; + } + } + } + + mOrientedRanges.haveOrientation = true; + + mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION; + mOrientedRanges.orientation.source = mSource; + mOrientedRanges.orientation.min = -M_PI_2; + mOrientedRanges.orientation.max = M_PI_2; + mOrientedRanges.orientation.flat = 0; + mOrientedRanges.orientation.fuzz = 0; + mOrientedRanges.orientation.resolution = 0; + } + + // Distance + mDistanceScale = 0; + if (mCalibration.distanceCalibration != Calibration::DISTANCE_CALIBRATION_NONE) { + if (mCalibration.distanceCalibration + == Calibration::DISTANCE_CALIBRATION_SCALED) { + if (mCalibration.haveDistanceScale) { + mDistanceScale = mCalibration.distanceScale; + } else { + mDistanceScale = 1.0f; + } + } + + mOrientedRanges.haveDistance = true; + + mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE; + mOrientedRanges.distance.source = mSource; + mOrientedRanges.distance.min = + mRawPointerAxes.distance.minValue * mDistanceScale; + mOrientedRanges.distance.max = + mRawPointerAxes.distance.maxValue * mDistanceScale; + mOrientedRanges.distance.flat = 0; + mOrientedRanges.distance.fuzz = + mRawPointerAxes.distance.fuzz * mDistanceScale; + mOrientedRanges.distance.resolution = 0; + } + + // Compute oriented precision, scales and ranges. + // Note that the maximum value reported is an inclusive maximum value so it is one + // unit less than the total width or height of surface. + switch (mSurfaceOrientation) { + case DISPLAY_ORIENTATION_90: + case DISPLAY_ORIENTATION_270: + mOrientedXPrecision = mYPrecision; + mOrientedYPrecision = mXPrecision; + + mOrientedRanges.x.min = mYTranslate; + mOrientedRanges.x.max = mSurfaceHeight + mYTranslate - 1; + mOrientedRanges.x.flat = 0; + mOrientedRanges.x.fuzz = 0; + mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale; + + mOrientedRanges.y.min = mXTranslate; + mOrientedRanges.y.max = mSurfaceWidth + mXTranslate - 1; + mOrientedRanges.y.flat = 0; + mOrientedRanges.y.fuzz = 0; + mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale; + break; + + default: + mOrientedXPrecision = mXPrecision; + mOrientedYPrecision = mYPrecision; + + mOrientedRanges.x.min = mXTranslate; + mOrientedRanges.x.max = mSurfaceWidth + mXTranslate - 1; + mOrientedRanges.x.flat = 0; + mOrientedRanges.x.fuzz = 0; + mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale; + + mOrientedRanges.y.min = mYTranslate; + mOrientedRanges.y.max = mSurfaceHeight + mYTranslate - 1; + mOrientedRanges.y.flat = 0; + mOrientedRanges.y.fuzz = 0; + mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale; + break; + } + + if (mDeviceMode == DEVICE_MODE_POINTER) { + // Compute pointer gesture detection parameters. + float rawDiagonal = hypotf(rawWidth, rawHeight); + float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight); + + // Scale movements such that one whole swipe of the touch pad covers a + // given area relative to the diagonal size of the display when no acceleration + // is applied. + // Assume that the touch pad has a square aspect ratio such that movements in + // X and Y of the same number of raw units cover the same physical distance. + mPointerXMovementScale = mConfig.pointerGestureMovementSpeedRatio + * displayDiagonal / rawDiagonal; + mPointerYMovementScale = mPointerXMovementScale; + + // Scale zooms to cover a smaller range of the display than movements do. + // This value determines the area around the pointer that is affected by freeform + // pointer gestures. + mPointerXZoomScale = mConfig.pointerGestureZoomSpeedRatio + * displayDiagonal / rawDiagonal; + mPointerYZoomScale = mPointerXZoomScale; + + // Max width between pointers to detect a swipe gesture is more than some fraction + // of the diagonal axis of the touch pad. Touches that are wider than this are + // translated into freeform gestures. + mPointerGestureMaxSwipeWidth = + mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal; + + // Abort current pointer usages because the state has changed. + abortPointerUsage(when, 0 /*policyFlags*/); + } + + // Inform the dispatcher about the changes. + *outResetNeeded = true; + bumpGeneration(); + } +} + +void TouchInputMapper::dumpSurface(String8& dump) { + dump.appendFormat(INDENT3 "Viewport: displayId=%d, orientation=%d, " + "logicalFrame=[%d, %d, %d, %d], " + "physicalFrame=[%d, %d, %d, %d], " + "deviceSize=[%d, %d]\n", + mViewport.displayId, mViewport.orientation, + mViewport.logicalLeft, mViewport.logicalTop, + mViewport.logicalRight, mViewport.logicalBottom, + mViewport.physicalLeft, mViewport.physicalTop, + mViewport.physicalRight, mViewport.physicalBottom, + mViewport.deviceWidth, mViewport.deviceHeight); + + dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mSurfaceWidth); + dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mSurfaceHeight); + dump.appendFormat(INDENT3 "SurfaceLeft: %d\n", mSurfaceLeft); + dump.appendFormat(INDENT3 "SurfaceTop: %d\n", mSurfaceTop); + dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mSurfaceOrientation); +} + +void TouchInputMapper::configureVirtualKeys() { + Vector virtualKeyDefinitions; + getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions); + + mVirtualKeys.clear(); + + if (virtualKeyDefinitions.size() == 0) { + return; + } + + mVirtualKeys.setCapacity(virtualKeyDefinitions.size()); + + int32_t touchScreenLeft = mRawPointerAxes.x.minValue; + int32_t touchScreenTop = mRawPointerAxes.y.minValue; + int32_t touchScreenWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1; + int32_t touchScreenHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1; + + for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) { + const VirtualKeyDefinition& virtualKeyDefinition = + virtualKeyDefinitions[i]; + + mVirtualKeys.add(); + VirtualKey& virtualKey = mVirtualKeys.editTop(); + + virtualKey.scanCode = virtualKeyDefinition.scanCode; + int32_t keyCode; + uint32_t flags; + if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode, 0, &keyCode, &flags)) { + ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring", + virtualKey.scanCode); + mVirtualKeys.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 / mSurfaceWidth + touchScreenLeft; + virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth) + * touchScreenWidth / mSurfaceWidth + touchScreenLeft; + virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) + * touchScreenHeight / mSurfaceHeight + touchScreenTop; + virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) + * touchScreenHeight / mSurfaceHeight + touchScreenTop; + } +} + +void TouchInputMapper::dumpVirtualKeys(String8& dump) { + if (!mVirtualKeys.isEmpty()) { + dump.append(INDENT3 "Virtual Keys:\n"); + + for (size_t i = 0; i < mVirtualKeys.size(); i++) { + const VirtualKey& virtualKey = mVirtualKeys.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; + + // Size + out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT; + String8 sizeCalibrationString; + if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) { + if (sizeCalibrationString == "none") { + out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE; + } else if (sizeCalibrationString == "geometric") { + out.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC; + } else if (sizeCalibrationString == "diameter") { + out.sizeCalibration = Calibration::SIZE_CALIBRATION_DIAMETER; + } else if (sizeCalibrationString == "box") { + out.sizeCalibration = Calibration::SIZE_CALIBRATION_BOX; + } else if (sizeCalibrationString == "area") { + out.sizeCalibration = Calibration::SIZE_CALIBRATION_AREA; + } else if (sizeCalibrationString != "default") { + ALOGW("Invalid value for touch.size.calibration: '%s'", + sizeCalibrationString.string()); + } + } + + out.haveSizeScale = in.tryGetProperty(String8("touch.size.scale"), + out.sizeScale); + out.haveSizeBias = in.tryGetProperty(String8("touch.size.bias"), + out.sizeBias); + out.haveSizeIsSummed = in.tryGetProperty(String8("touch.size.isSummed"), + out.sizeIsSummed); + + // Pressure + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT; + String8 pressureCalibrationString; + if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) { + if (pressureCalibrationString == "none") { + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE; + } else if (pressureCalibrationString == "physical") { + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL; + } else if (pressureCalibrationString == "amplitude") { + out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE; + } else if (pressureCalibrationString != "default") { + ALOGW("Invalid value for touch.pressure.calibration: '%s'", + pressureCalibrationString.string()); + } + } + + out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"), + out.pressureScale); + + // Orientation + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT; + String8 orientationCalibrationString; + if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) { + if (orientationCalibrationString == "none") { + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE; + } else if (orientationCalibrationString == "interpolated") { + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED; + } else if (orientationCalibrationString == "vector") { + out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR; + } else if (orientationCalibrationString != "default") { + ALOGW("Invalid value for touch.orientation.calibration: '%s'", + orientationCalibrationString.string()); + } + } + + // Distance + out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_DEFAULT; + String8 distanceCalibrationString; + if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) { + if (distanceCalibrationString == "none") { + out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE; + } else if (distanceCalibrationString == "scaled") { + out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED; + } else if (distanceCalibrationString != "default") { + ALOGW("Invalid value for touch.distance.calibration: '%s'", + distanceCalibrationString.string()); + } + } + + out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"), + out.distanceScale); + + out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_DEFAULT; + String8 coverageCalibrationString; + if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) { + if (coverageCalibrationString == "none") { + out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE; + } else if (coverageCalibrationString == "box") { + out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_BOX; + } else if (coverageCalibrationString != "default") { + ALOGW("Invalid value for touch.coverage.calibration: '%s'", + coverageCalibrationString.string()); + } + } +} + +void TouchInputMapper::resolveCalibration() { + // Size + if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) { + if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DEFAULT) { + mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC; + } + } else { + mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE; + } + + // Pressure + if (mRawPointerAxes.pressure.valid) { + if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_DEFAULT) { + mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL; + } + } else { + mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE; + } + + // Orientation + if (mRawPointerAxes.orientation.valid) { + if (mCalibration.orientationCalibration == Calibration::ORIENTATION_CALIBRATION_DEFAULT) { + mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED; + } + } else { + mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE; + } + + // Distance + if (mRawPointerAxes.distance.valid) { + if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_DEFAULT) { + mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED; + } + } else { + mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE; + } + + // Coverage + if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_DEFAULT) { + mCalibration.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE; + } +} + +void TouchInputMapper::dumpCalibration(String8& dump) { + dump.append(INDENT3 "Calibration:\n"); + + // Size + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_NONE: + dump.append(INDENT4 "touch.size.calibration: none\n"); + break; + case Calibration::SIZE_CALIBRATION_GEOMETRIC: + dump.append(INDENT4 "touch.size.calibration: geometric\n"); + break; + case Calibration::SIZE_CALIBRATION_DIAMETER: + dump.append(INDENT4 "touch.size.calibration: diameter\n"); + break; + case Calibration::SIZE_CALIBRATION_BOX: + dump.append(INDENT4 "touch.size.calibration: box\n"); + break; + case Calibration::SIZE_CALIBRATION_AREA: + dump.append(INDENT4 "touch.size.calibration: area\n"); + break; + default: + ALOG_ASSERT(false); + } + + if (mCalibration.haveSizeScale) { + dump.appendFormat(INDENT4 "touch.size.scale: %0.3f\n", + mCalibration.sizeScale); + } + + if (mCalibration.haveSizeBias) { + dump.appendFormat(INDENT4 "touch.size.bias: %0.3f\n", + mCalibration.sizeBias); + } + + if (mCalibration.haveSizeIsSummed) { + dump.appendFormat(INDENT4 "touch.size.isSummed: %s\n", + toString(mCalibration.sizeIsSummed)); + } + + // 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: + ALOG_ASSERT(false); + } + + if (mCalibration.havePressureScale) { + dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n", + mCalibration.pressureScale); + } + + // 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; + case Calibration::ORIENTATION_CALIBRATION_VECTOR: + dump.append(INDENT4 "touch.orientation.calibration: vector\n"); + break; + default: + ALOG_ASSERT(false); + } + + // Distance + switch (mCalibration.distanceCalibration) { + case Calibration::DISTANCE_CALIBRATION_NONE: + dump.append(INDENT4 "touch.distance.calibration: none\n"); + break; + case Calibration::DISTANCE_CALIBRATION_SCALED: + dump.append(INDENT4 "touch.distance.calibration: scaled\n"); + break; + default: + ALOG_ASSERT(false); + } + + if (mCalibration.haveDistanceScale) { + dump.appendFormat(INDENT4 "touch.distance.scale: %0.3f\n", + mCalibration.distanceScale); + } + + switch (mCalibration.coverageCalibration) { + case Calibration::COVERAGE_CALIBRATION_NONE: + dump.append(INDENT4 "touch.coverage.calibration: none\n"); + break; + case Calibration::COVERAGE_CALIBRATION_BOX: + dump.append(INDENT4 "touch.coverage.calibration: box\n"); + break; + default: + ALOG_ASSERT(false); + } +} + +void TouchInputMapper::reset(nsecs_t when) { + mCursorButtonAccumulator.reset(getDevice()); + mCursorScrollAccumulator.reset(getDevice()); + mTouchButtonAccumulator.reset(getDevice()); + + mPointerVelocityControl.reset(); + mWheelXVelocityControl.reset(); + mWheelYVelocityControl.reset(); + + mCurrentRawPointerData.clear(); + mLastRawPointerData.clear(); + mCurrentCookedPointerData.clear(); + mLastCookedPointerData.clear(); + mCurrentButtonState = 0; + mLastButtonState = 0; + mCurrentRawVScroll = 0; + mCurrentRawHScroll = 0; + mCurrentFingerIdBits.clear(); + mLastFingerIdBits.clear(); + mCurrentStylusIdBits.clear(); + mLastStylusIdBits.clear(); + mCurrentMouseIdBits.clear(); + mLastMouseIdBits.clear(); + mPointerUsage = POINTER_USAGE_NONE; + mSentHoverEnter = false; + mDownTime = 0; + + mCurrentVirtualKey.down = false; + + mPointerGesture.reset(); + mPointerSimple.reset(); + + if (mPointerController != NULL) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + mPointerController->clearSpots(); + } + + InputMapper::reset(when); +} + +void TouchInputMapper::process(const RawEvent* rawEvent) { + mCursorButtonAccumulator.process(rawEvent); + mCursorScrollAccumulator.process(rawEvent); + mTouchButtonAccumulator.process(rawEvent); + + if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + sync(rawEvent->when); + } +} + +void TouchInputMapper::sync(nsecs_t when) { + // Sync button state. + mCurrentButtonState = mTouchButtonAccumulator.getButtonState() + | mCursorButtonAccumulator.getButtonState(); + + // Sync scroll state. + mCurrentRawVScroll = mCursorScrollAccumulator.getRelativeVWheel(); + mCurrentRawHScroll = mCursorScrollAccumulator.getRelativeHWheel(); + mCursorScrollAccumulator.finishSync(); + + // Sync touch state. + bool havePointerIds = true; + mCurrentRawPointerData.clear(); + syncTouch(when, &havePointerIds); + +#if DEBUG_RAW_EVENTS + if (!havePointerIds) { + ALOGD("syncTouch: pointerCount %d -> %d, no pointer ids", + mLastRawPointerData.pointerCount, + mCurrentRawPointerData.pointerCount); + } else { + ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, " + "hovering ids 0x%08x -> 0x%08x", + mLastRawPointerData.pointerCount, + mCurrentRawPointerData.pointerCount, + mLastRawPointerData.touchingIdBits.value, + mCurrentRawPointerData.touchingIdBits.value, + mLastRawPointerData.hoveringIdBits.value, + mCurrentRawPointerData.hoveringIdBits.value); + } +#endif + + // Reset state that we will compute below. + mCurrentFingerIdBits.clear(); + mCurrentStylusIdBits.clear(); + mCurrentMouseIdBits.clear(); + mCurrentCookedPointerData.clear(); + + if (mDeviceMode == DEVICE_MODE_DISABLED) { + // Drop all input if the device is disabled. + mCurrentRawPointerData.clear(); + mCurrentButtonState = 0; + } else { + // Preprocess pointer data. + if (!havePointerIds) { + assignPointerIds(); + } + + // Handle policy on initial down or hover events. + uint32_t policyFlags = 0; + bool initialDown = mLastRawPointerData.pointerCount == 0 + && mCurrentRawPointerData.pointerCount != 0; + bool buttonsPressed = mCurrentButtonState & ~mLastButtonState; + if (initialDown || buttonsPressed) { + // If this is a touch screen, hide the pointer on an initial down. + if (mDeviceMode == DEVICE_MODE_DIRECT) { + getContext()->fadePointer(); + } + + // Initial downs on external touch devices should wake the device. + // We don't do this for internal touch screens to prevent them from waking + // up in your pocket. + // TODO: Use the input device configuration to control this behavior more finely. + if (getDevice()->isExternal()) { + policyFlags |= POLICY_FLAG_WAKE_DROPPED; + } + } + + // Synthesize key down from raw buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource, + policyFlags, mLastButtonState, mCurrentButtonState); + + // Consume raw off-screen touches before cooking pointer data. + // If touches are consumed, subsequent code will not receive any pointer data. + if (consumeRawTouches(when, policyFlags)) { + mCurrentRawPointerData.clear(); + } + + // Cook pointer data. This call populates the mCurrentCookedPointerData structure + // with cooked pointer data that has the same ids and indices as the raw data. + // The following code can use either the raw or cooked data, as needed. + cookPointerData(); + + // Dispatch the touches either directly or by translation through a pointer on screen. + if (mDeviceMode == DEVICE_MODE_POINTER) { + for (BitSet32 idBits(mCurrentRawPointerData.touchingIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id); + if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS + || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) { + mCurrentStylusIdBits.markBit(id); + } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER + || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + mCurrentFingerIdBits.markBit(id); + } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) { + mCurrentMouseIdBits.markBit(id); + } + } + for (BitSet32 idBits(mCurrentRawPointerData.hoveringIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id); + if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS + || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) { + mCurrentStylusIdBits.markBit(id); + } + } + + // Stylus takes precedence over all tools, then mouse, then finger. + PointerUsage pointerUsage = mPointerUsage; + if (!mCurrentStylusIdBits.isEmpty()) { + mCurrentMouseIdBits.clear(); + mCurrentFingerIdBits.clear(); + pointerUsage = POINTER_USAGE_STYLUS; + } else if (!mCurrentMouseIdBits.isEmpty()) { + mCurrentFingerIdBits.clear(); + pointerUsage = POINTER_USAGE_MOUSE; + } else if (!mCurrentFingerIdBits.isEmpty() || isPointerDown(mCurrentButtonState)) { + pointerUsage = POINTER_USAGE_GESTURES; + } + + dispatchPointerUsage(when, policyFlags, pointerUsage); + } else { + if (mDeviceMode == DEVICE_MODE_DIRECT + && mConfig.showTouches && mPointerController != NULL) { + mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT); + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + + mPointerController->setButtonState(mCurrentButtonState); + mPointerController->setSpots(mCurrentCookedPointerData.pointerCoords, + mCurrentCookedPointerData.idToIndex, + mCurrentCookedPointerData.touchingIdBits); + } + + dispatchHoverExit(when, policyFlags); + dispatchTouches(when, policyFlags); + dispatchHoverEnterAndMove(when, policyFlags); + } + + // Synthesize key up from raw buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource, + policyFlags, mLastButtonState, mCurrentButtonState); + } + + // Copy current touch to last touch in preparation for the next cycle. + mLastRawPointerData.copyFrom(mCurrentRawPointerData); + mLastCookedPointerData.copyFrom(mCurrentCookedPointerData); + mLastButtonState = mCurrentButtonState; + mLastFingerIdBits = mCurrentFingerIdBits; + mLastStylusIdBits = mCurrentStylusIdBits; + mLastMouseIdBits = mCurrentMouseIdBits; + + // Clear some transient state. + mCurrentRawVScroll = 0; + mCurrentRawHScroll = 0; +} + +void TouchInputMapper::timeoutExpired(nsecs_t when) { + if (mDeviceMode == DEVICE_MODE_POINTER) { + if (mPointerUsage == POINTER_USAGE_GESTURES) { + dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/); + } + } +} + +bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) { + // Check for release of a virtual key. + if (mCurrentVirtualKey.down) { + if (mCurrentRawPointerData.touchingIdBits.isEmpty()) { + // Pointer went up while virtual key was down. + mCurrentVirtualKey.down = false; + if (!mCurrentVirtualKey.ignored) { +#if DEBUG_VIRTUAL_KEYS + ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); +#endif + dispatchVirtualKey(when, policyFlags, + AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + } + return true; + } + + if (mCurrentRawPointerData.touchingIdBits.count() == 1) { + uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit(); + const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id); + const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y); + if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) { + // Pointer is still within the space of the virtual key. + return true; + } + } + + // Pointer left virtual key area or another pointer also went down. + // Send key cancellation but do not consume the touch yet. + // This is useful when the user swipes through from the virtual key area + // into the main display surface. + mCurrentVirtualKey.down = false; + if (!mCurrentVirtualKey.ignored) { +#if DEBUG_VIRTUAL_KEYS + ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); +#endif + dispatchVirtualKey(when, policyFlags, + AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY + | AKEY_EVENT_FLAG_CANCELED); + } + } + + if (mLastRawPointerData.touchingIdBits.isEmpty() + && !mCurrentRawPointerData.touchingIdBits.isEmpty()) { + // Pointer just went down. Check for virtual key press or off-screen touches. + uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit(); + const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id); + if (!isPointInsideSurface(pointer.x, pointer.y)) { + // If exactly one pointer went down, check for virtual key hit. + // Otherwise we will drop the entire stroke. + if (mCurrentRawPointerData.touchingIdBits.count() == 1) { + const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y); + if (virtualKey) { + mCurrentVirtualKey.down = true; + mCurrentVirtualKey.downTime = when; + mCurrentVirtualKey.keyCode = virtualKey->keyCode; + mCurrentVirtualKey.scanCode = virtualKey->scanCode; + mCurrentVirtualKey.ignored = mContext->shouldDropVirtualKey( + when, getDevice(), virtualKey->keyCode, virtualKey->scanCode); + + if (!mCurrentVirtualKey.ignored) { +#if DEBUG_VIRTUAL_KEYS + ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, + mCurrentVirtualKey.scanCode); +#endif + dispatchVirtualKey(when, policyFlags, + AKEY_EVENT_ACTION_DOWN, + AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + } + } + } + return true; + } + } + + // Disable all virtual key touches that happen within a short time interval of the + // most recent touch within the screen area. The idea is to filter out stray + // virtual key presses when interacting with the touch screen. + // + // Problems we're trying to solve: + // + // 1. While scrolling a list or dragging the window shade, the user swipes down into a + // virtual key area that is implemented by a separate touch panel and accidentally + // triggers a virtual key. + // + // 2. While typing in the on screen keyboard, the user taps slightly outside the screen + // area and accidentally triggers a virtual key. This often happens when virtual keys + // are layed out below the screen near to where the on screen keyboard's space bar + // is displayed. + if (mConfig.virtualKeyQuietTime > 0 && !mCurrentRawPointerData.touchingIdBits.isEmpty()) { + mContext->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime); + } + return false; +} + +void TouchInputMapper::dispatchVirtualKey(nsecs_t when, uint32_t policyFlags, + int32_t keyEventAction, int32_t keyEventFlags) { + int32_t keyCode = mCurrentVirtualKey.keyCode; + int32_t scanCode = mCurrentVirtualKey.scanCode; + nsecs_t downTime = mCurrentVirtualKey.downTime; + int32_t metaState = mContext->getGlobalMetaState(); + policyFlags |= POLICY_FLAG_VIRTUAL; + + NotifyKeyArgs args(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags, + keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime); + getListener()->notifyKey(&args); +} + +void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) { + BitSet32 currentIdBits = mCurrentCookedPointerData.touchingIdBits; + BitSet32 lastIdBits = mLastCookedPointerData.touchingIdBits; + int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mCurrentButtonState; + + if (currentIdBits == lastIdBits) { + if (!currentIdBits.isEmpty()) { + // No pointer id changes so this is a move event. + // The listener takes care of batching moves so we don't have to deal with that here. + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentCookedPointerData.pointerProperties, + mCurrentCookedPointerData.pointerCoords, + mCurrentCookedPointerData.idToIndex, + currentIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime); + } + } else { + // There may be pointers going up and pointers going down and pointers moving + // all at the same time. + BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value); + BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value); + BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value); + BitSet32 dispatchedIdBits(lastIdBits.value); + + // Update last coordinates of pointers that have moved so that we observe the new + // pointer positions at the same time as other pointers that have just gone up. + bool moveNeeded = updateMovedPointers( + mCurrentCookedPointerData.pointerProperties, + mCurrentCookedPointerData.pointerCoords, + mCurrentCookedPointerData.idToIndex, + mLastCookedPointerData.pointerProperties, + mLastCookedPointerData.pointerCoords, + mLastCookedPointerData.idToIndex, + moveIdBits); + if (buttonState != mLastButtonState) { + moveNeeded = true; + } + + // Dispatch pointer up events. + while (!upIdBits.isEmpty()) { + uint32_t upId = upIdBits.clearFirstMarkedBit(); + + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, metaState, buttonState, 0, + mLastCookedPointerData.pointerProperties, + mLastCookedPointerData.pointerCoords, + mLastCookedPointerData.idToIndex, + dispatchedIdBits, upId, + mOrientedXPrecision, mOrientedYPrecision, mDownTime); + dispatchedIdBits.clearBit(upId); + } + + // Dispatch move events if any of the remaining pointers moved from their old locations. + // Although applications receive new locations as part of individual pointer up + // events, they do not generally handle them except when presented in a move event. + if (moveNeeded) { + ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value); + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, 0, + mCurrentCookedPointerData.pointerProperties, + mCurrentCookedPointerData.pointerCoords, + mCurrentCookedPointerData.idToIndex, + dispatchedIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime); + } + + // Dispatch pointer down events using the new pointer locations. + while (!downIdBits.isEmpty()) { + uint32_t downId = downIdBits.clearFirstMarkedBit(); + dispatchedIdBits.markBit(downId); + + if (dispatchedIdBits.count() == 1) { + // First pointer is going down. Set down time. + mDownTime = when; + } + + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0, + mCurrentCookedPointerData.pointerProperties, + mCurrentCookedPointerData.pointerCoords, + mCurrentCookedPointerData.idToIndex, + dispatchedIdBits, downId, + mOrientedXPrecision, mOrientedYPrecision, mDownTime); + } + } +} + +void TouchInputMapper::dispatchHoverExit(nsecs_t when, uint32_t policyFlags) { + if (mSentHoverEnter && + (mCurrentCookedPointerData.hoveringIdBits.isEmpty() + || !mCurrentCookedPointerData.touchingIdBits.isEmpty())) { + int32_t metaState = getContext()->getGlobalMetaState(); + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0, + mLastCookedPointerData.pointerProperties, + mLastCookedPointerData.pointerCoords, + mLastCookedPointerData.idToIndex, + mLastCookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mSentHoverEnter = false; + } +} + +void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags) { + if (mCurrentCookedPointerData.touchingIdBits.isEmpty() + && !mCurrentCookedPointerData.hoveringIdBits.isEmpty()) { + int32_t metaState = getContext()->getGlobalMetaState(); + if (!mSentHoverEnter) { + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0, + mCurrentCookedPointerData.pointerProperties, + mCurrentCookedPointerData.pointerCoords, + mCurrentCookedPointerData.idToIndex, + mCurrentCookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mSentHoverEnter = true; + } + + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0, + mCurrentCookedPointerData.pointerProperties, + mCurrentCookedPointerData.pointerCoords, + mCurrentCookedPointerData.idToIndex, + mCurrentCookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime); + } +} + +void TouchInputMapper::cookPointerData() { + uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount; + + mCurrentCookedPointerData.clear(); + mCurrentCookedPointerData.pointerCount = currentPointerCount; + mCurrentCookedPointerData.hoveringIdBits = mCurrentRawPointerData.hoveringIdBits; + mCurrentCookedPointerData.touchingIdBits = mCurrentRawPointerData.touchingIdBits; + + // Walk through the the active pointers and map device coordinates onto + // surface coordinates and adjust for display orientation. + for (uint32_t i = 0; i < currentPointerCount; i++) { + const RawPointerData::Pointer& in = mCurrentRawPointerData.pointers[i]; + + // Size + float touchMajor, touchMinor, toolMajor, toolMinor, size; + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_GEOMETRIC: + case Calibration::SIZE_CALIBRATION_DIAMETER: + case Calibration::SIZE_CALIBRATION_BOX: + case Calibration::SIZE_CALIBRATION_AREA: + if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) { + touchMajor = in.touchMajor; + touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor; + toolMajor = in.toolMajor; + toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor; + size = mRawPointerAxes.touchMinor.valid + ? avg(in.touchMajor, in.touchMinor) : in.touchMajor; + } else if (mRawPointerAxes.touchMajor.valid) { + toolMajor = touchMajor = in.touchMajor; + toolMinor = touchMinor = mRawPointerAxes.touchMinor.valid + ? in.touchMinor : in.touchMajor; + size = mRawPointerAxes.touchMinor.valid + ? avg(in.touchMajor, in.touchMinor) : in.touchMajor; + } else if (mRawPointerAxes.toolMajor.valid) { + touchMajor = toolMajor = in.toolMajor; + touchMinor = toolMinor = mRawPointerAxes.toolMinor.valid + ? in.toolMinor : in.toolMajor; + size = mRawPointerAxes.toolMinor.valid + ? avg(in.toolMajor, in.toolMinor) : in.toolMajor; + } else { + ALOG_ASSERT(false, "No touch or tool axes. " + "Size calibration should have been resolved to NONE."); + touchMajor = 0; + touchMinor = 0; + toolMajor = 0; + toolMinor = 0; + size = 0; + } + + if (mCalibration.haveSizeIsSummed && mCalibration.sizeIsSummed) { + uint32_t touchingCount = mCurrentRawPointerData.touchingIdBits.count(); + if (touchingCount > 1) { + touchMajor /= touchingCount; + touchMinor /= touchingCount; + toolMajor /= touchingCount; + toolMinor /= touchingCount; + size /= touchingCount; + } + } + + if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_GEOMETRIC) { + touchMajor *= mGeometricScale; + touchMinor *= mGeometricScale; + toolMajor *= mGeometricScale; + toolMinor *= mGeometricScale; + } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_AREA) { + touchMajor = touchMajor > 0 ? sqrtf(touchMajor) : 0; + touchMinor = touchMajor; + toolMajor = toolMajor > 0 ? sqrtf(toolMajor) : 0; + toolMinor = toolMajor; + } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DIAMETER) { + touchMinor = touchMajor; + toolMinor = toolMajor; + } + + mCalibration.applySizeScaleAndBias(&touchMajor); + mCalibration.applySizeScaleAndBias(&touchMinor); + mCalibration.applySizeScaleAndBias(&toolMajor); + mCalibration.applySizeScaleAndBias(&toolMinor); + size *= mSizeScale; + break; + default: + touchMajor = 0; + touchMinor = 0; + toolMajor = 0; + toolMinor = 0; + size = 0; + break; + } + + // Pressure + float pressure; + switch (mCalibration.pressureCalibration) { + case Calibration::PRESSURE_CALIBRATION_PHYSICAL: + case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: + pressure = in.pressure * mPressureScale; + break; + default: + pressure = in.isHovering ? 0 : 1; + break; + } + + // Tilt and Orientation + float tilt; + float orientation; + if (mHaveTilt) { + float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale; + float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale; + orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)); + tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle)); + } else { + tilt = 0; + + switch (mCalibration.orientationCalibration) { + case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: + orientation = in.orientation * mOrientationScale; + break; + case Calibration::ORIENTATION_CALIBRATION_VECTOR: { + int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); + int32_t c2 = signExtendNybble(in.orientation & 0x0f); + if (c1 != 0 || c2 != 0) { + orientation = atan2f(c1, c2) * 0.5f; + float confidence = hypotf(c1, c2); + float scale = 1.0f + confidence / 16.0f; + touchMajor *= scale; + touchMinor /= scale; + toolMajor *= scale; + toolMinor /= scale; + } else { + orientation = 0; + } + break; + } + default: + orientation = 0; + } + } + + // Distance + float distance; + switch (mCalibration.distanceCalibration) { + case Calibration::DISTANCE_CALIBRATION_SCALED: + distance = in.distance * mDistanceScale; + break; + default: + distance = 0; + } + + // Coverage + int32_t rawLeft, rawTop, rawRight, rawBottom; + switch (mCalibration.coverageCalibration) { + case Calibration::COVERAGE_CALIBRATION_BOX: + rawLeft = (in.toolMinor & 0xffff0000) >> 16; + rawRight = in.toolMinor & 0x0000ffff; + rawBottom = in.toolMajor & 0x0000ffff; + rawTop = (in.toolMajor & 0xffff0000) >> 16; + break; + default: + rawLeft = rawTop = rawRight = rawBottom = 0; + break; + } + + // X, Y, and the bounding box for coverage information + // Adjust coords for surface orientation. + float x, y, left, top, right, bottom; + switch (mSurfaceOrientation) { + case DISPLAY_ORIENTATION_90: + x = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; + y = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate; + left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; + right = float(rawBottom- mRawPointerAxes.y.minValue) * mYScale + mYTranslate; + bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate; + top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate; + orientation -= M_PI_2; + if (orientation < mOrientedRanges.orientation.min) { + orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min); + } + break; + case DISPLAY_ORIENTATION_180: + x = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate; + y = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate; + left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate; + right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate; + bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate; + top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate; + orientation -= M_PI; + if (orientation < mOrientedRanges.orientation.min) { + orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min); + } + break; + case DISPLAY_ORIENTATION_270: + x = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate; + y = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; + left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate; + right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate; + bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; + top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; + orientation += M_PI_2; + if (orientation > mOrientedRanges.orientation.max) { + orientation -= (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min); + } + break; + default: + x = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; + y = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; + left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; + right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate; + bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; + top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate; + break; + } + + // Write output coords. + PointerCoords& out = mCurrentCookedPointerData.pointerCoords[i]; + out.clear(); + out.setAxisValue(AMOTION_EVENT_AXIS_X, x); + out.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); + out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt); + out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance); + if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) { + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left); + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top); + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right); + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom); + } else { + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); + } + + // Write output properties. + PointerProperties& properties = mCurrentCookedPointerData.pointerProperties[i]; + uint32_t id = in.id; + properties.clear(); + properties.id = id; + properties.toolType = in.toolType; + + // Write id index. + mCurrentCookedPointerData.idToIndex[id] = i; + } +} + +void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, + PointerUsage pointerUsage) { + if (pointerUsage != mPointerUsage) { + abortPointerUsage(when, policyFlags); + mPointerUsage = pointerUsage; + } + + switch (mPointerUsage) { + case POINTER_USAGE_GESTURES: + dispatchPointerGestures(when, policyFlags, false /*isTimeout*/); + break; + case POINTER_USAGE_STYLUS: + dispatchPointerStylus(when, policyFlags); + break; + case POINTER_USAGE_MOUSE: + dispatchPointerMouse(when, policyFlags); + break; + default: + break; + } +} + +void TouchInputMapper::abortPointerUsage(nsecs_t when, uint32_t policyFlags) { + switch (mPointerUsage) { + case POINTER_USAGE_GESTURES: + abortPointerGestures(when, policyFlags); + break; + case POINTER_USAGE_STYLUS: + abortPointerStylus(when, policyFlags); + break; + case POINTER_USAGE_MOUSE: + abortPointerMouse(when, policyFlags); + break; + default: + break; + } + + mPointerUsage = POINTER_USAGE_NONE; +} + +void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, + bool isTimeout) { + // Update current gesture coordinates. + bool cancelPreviousGesture, finishPreviousGesture; + bool sendEvents = preparePointerGestures(when, + &cancelPreviousGesture, &finishPreviousGesture, isTimeout); + if (!sendEvents) { + return; + } + if (finishPreviousGesture) { + cancelPreviousGesture = false; + } + + // Update the pointer presentation and spots. + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT); + if (finishPreviousGesture || cancelPreviousGesture) { + mPointerController->clearSpots(); + } + mPointerController->setSpots(mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits); + } else { + mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER); + } + + // Show or hide the pointer if needed. + switch (mPointerGesture.currentGestureMode) { + case PointerGesture::NEUTRAL: + case PointerGesture::QUIET: + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS + && (mPointerGesture.lastGestureMode == PointerGesture::SWIPE + || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)) { + // Remind the user of where the pointer is after finishing a gesture with spots. + mPointerController->unfade(PointerControllerInterface::TRANSITION_GRADUAL); + } + break; + case PointerGesture::TAP: + case PointerGesture::TAP_DRAG: + case PointerGesture::BUTTON_CLICK_OR_DRAG: + case PointerGesture::HOVER: + case PointerGesture::PRESS: + // Unfade the pointer when the current gesture manipulates the + // area directly under the pointer. + mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE); + break; + case PointerGesture::SWIPE: + case PointerGesture::FREEFORM: + // Fade the pointer when the current gesture manipulates a different + // area and there are spots to guide the user experience. + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + } else { + mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE); + } + break; + } + + // Send events! + int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mCurrentButtonState; + + // Update last coordinates of pointers that have moved so that we observe the new + // pointer positions at the same time as other pointers that have just gone up. + bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP + || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG + || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG + || mPointerGesture.currentGestureMode == PointerGesture::PRESS + || mPointerGesture.currentGestureMode == PointerGesture::SWIPE + || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM; + bool moveNeeded = false; + if (down && !cancelPreviousGesture && !finishPreviousGesture + && !mPointerGesture.lastGestureIdBits.isEmpty() + && !mPointerGesture.currentGestureIdBits.isEmpty()) { + BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value + & mPointerGesture.lastGestureIdBits.value); + moveNeeded = updateMovedPointers(mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + movedGestureIdBits); + if (buttonState != mLastButtonState) { + moveNeeded = true; + } + } + + // Send motion events for all pointers that went up or were canceled. + BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits); + if (!dispatchedGestureIdBits.isEmpty()) { + if (cancelPreviousGesture) { + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clear(); + } else { + BitSet32 upGestureIdBits; + if (finishPreviousGesture) { + upGestureIdBits = dispatchedGestureIdBits; + } else { + upGestureIdBits.value = dispatchedGestureIdBits.value + & ~mPointerGesture.currentGestureIdBits.value; + } + while (!upGestureIdBits.isEmpty()) { + uint32_t id = upGestureIdBits.clearFirstMarkedBit(); + + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clearBit(id); + } + } + } + + // Send motion events for all pointers that moved. + if (moveNeeded) { + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Send motion events for all pointers that went down. + if (down) { + BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value + & ~dispatchedGestureIdBits.value); + while (!downGestureIdBits.isEmpty()) { + uint32_t id = downGestureIdBits.clearFirstMarkedBit(); + dispatchedGestureIdBits.markBit(id); + + if (dispatchedGestureIdBits.count() == 1) { + mPointerGesture.downTime = when; + } + + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + } + } + + // Send motion events for hover. + if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) { + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } else if (dispatchedGestureIdBits.isEmpty() + && !mPointerGesture.lastGestureIdBits.isEmpty()) { + // Synthesize a hover move event after all pointers go up to indicate that + // the pointer is hovering again even if the user is not currently touching + // the touch pad. This ensures that a view will receive a fresh hover enter + // event after a tap. + float x, y; + mPointerController->getPosition(&x, &y); + + PointerProperties pointerProperties; + pointerProperties.clear(); + pointerProperties.id = 0; + pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + + PointerCoords pointerCoords; + pointerCoords.clear(); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mViewport.displayId, 1, &pointerProperties, &pointerCoords, + 0, 0, mPointerGesture.downTime); + getListener()->notifyMotion(&args); + } + + // Update state. + mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode; + if (!down) { + mPointerGesture.lastGestureIdBits.clear(); + } else { + mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits; + for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + mPointerGesture.lastGestureProperties[index].copyFrom( + mPointerGesture.currentGestureProperties[index]); + mPointerGesture.lastGestureCoords[index].copyFrom( + mPointerGesture.currentGestureCoords[index]); + mPointerGesture.lastGestureIdToIndex[id] = index; + } + } +} + +void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) { + // Cancel previously dispatches pointers. + if (!mPointerGesture.lastGestureIdBits.isEmpty()) { + int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mCurrentButtonState; + dispatchMotion(when, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + mPointerGesture.lastGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Reset the current pointer gesture. + mPointerGesture.reset(); + mPointerVelocityControl.reset(); + + // Remove any current spots. + if (mPointerController != NULL) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + mPointerController->clearSpots(); + } +} + +bool TouchInputMapper::preparePointerGestures(nsecs_t when, + bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) { + *outCancelPreviousGesture = false; + *outFinishPreviousGesture = false; + + // Handle TAP timeout. + if (isTimeout) { +#if DEBUG_GESTURES + ALOGD("Gestures: Processing timeout"); +#endif + + if (mPointerGesture.lastGestureMode == PointerGesture::TAP) { + if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) { + // The tap/drag timeout has not yet expired. + getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime + + mConfig.pointerGestureTapDragInterval); + } else { + // The tap is finished. +#if DEBUG_GESTURES + ALOGD("Gestures: TAP finished"); +#endif + *outFinishPreviousGesture = true; + + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; + mPointerGesture.currentGestureIdBits.clear(); + + mPointerVelocityControl.reset(); + return true; + } + } + + // We did not handle this timeout. + return false; + } + + const uint32_t currentFingerCount = mCurrentFingerIdBits.count(); + const uint32_t lastFingerCount = mLastFingerIdBits.count(); + + // Update the velocity tracker. + { + VelocityTracker::Position positions[MAX_POINTERS]; + uint32_t count = 0; + for (BitSet32 idBits(mCurrentFingerIdBits); !idBits.isEmpty(); count++) { + uint32_t id = idBits.clearFirstMarkedBit(); + const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id); + positions[count].x = pointer.x * mPointerXMovementScale; + positions[count].y = pointer.y * mPointerYMovementScale; + } + mPointerGesture.velocityTracker.addMovement(when, + mCurrentFingerIdBits, positions); + } + + // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning + // to NEUTRAL, then we should not generate tap event. + if (mPointerGesture.lastGestureMode != PointerGesture::HOVER + && mPointerGesture.lastGestureMode != PointerGesture::TAP + && mPointerGesture.lastGestureMode != PointerGesture::TAP_DRAG) { + mPointerGesture.resetTap(); + } + + // Pick a new active touch id if needed. + // Choose an arbitrary pointer that just went down, if there is one. + // Otherwise choose an arbitrary remaining pointer. + // This guarantees we always have an active touch id when there is at least one pointer. + // We keep the same active touch id for as long as possible. + bool activeTouchChanged = false; + int32_t lastActiveTouchId = mPointerGesture.activeTouchId; + int32_t activeTouchId = lastActiveTouchId; + if (activeTouchId < 0) { + if (!mCurrentFingerIdBits.isEmpty()) { + activeTouchChanged = true; + activeTouchId = mPointerGesture.activeTouchId = + mCurrentFingerIdBits.firstMarkedBit(); + mPointerGesture.firstTouchTime = when; + } + } else if (!mCurrentFingerIdBits.hasBit(activeTouchId)) { + activeTouchChanged = true; + if (!mCurrentFingerIdBits.isEmpty()) { + activeTouchId = mPointerGesture.activeTouchId = + mCurrentFingerIdBits.firstMarkedBit(); + } else { + activeTouchId = mPointerGesture.activeTouchId = -1; + } + } + + // Determine whether we are in quiet time. + bool isQuietTime = false; + if (activeTouchId < 0) { + mPointerGesture.resetQuietTime(); + } else { + isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval; + if (!isQuietTime) { + if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS + || mPointerGesture.lastGestureMode == PointerGesture::SWIPE + || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM) + && currentFingerCount < 2) { + // Enter quiet time when exiting swipe or freeform state. + // This is to prevent accidentally entering the hover state and flinging the + // pointer when finishing a swipe and there is still one pointer left onscreen. + isQuietTime = true; + } else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG + && currentFingerCount >= 2 + && !isPointerDown(mCurrentButtonState)) { + // Enter quiet time when releasing the button and there are still two or more + // fingers down. This may indicate that one finger was used to press the button + // but it has not gone up yet. + isQuietTime = true; + } + if (isQuietTime) { + mPointerGesture.quietTime = when; + } + } + } + + // Switch states based on button and pointer state. + if (isQuietTime) { + // Case 1: Quiet time. (QUIET) +#if DEBUG_GESTURES + ALOGD("Gestures: QUIET for next %0.3fms", (mPointerGesture.quietTime + + mConfig.pointerGestureQuietInterval - when) * 0.000001f); +#endif + if (mPointerGesture.lastGestureMode != PointerGesture::QUIET) { + *outFinishPreviousGesture = true; + } + + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::QUIET; + mPointerGesture.currentGestureIdBits.clear(); + + mPointerVelocityControl.reset(); + } else if (isPointerDown(mCurrentButtonState)) { + // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG) + // The pointer follows the active touch point. + // Emit DOWN, MOVE, UP events at the pointer location. + // + // Only the active touch matters; other fingers are ignored. This policy helps + // to handle the case where the user places a second finger on the touch pad + // to apply the necessary force to depress an integrated button below the surface. + // We don't want the second finger to be delivered to applications. + // + // For this to work well, we need to make sure to track the pointer that is really + // active. If the user first puts one finger down to click then adds another + // finger to drag then the active pointer should switch to the finger that is + // being dragged. +#if DEBUG_GESTURES + ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, " + "currentFingerCount=%d", activeTouchId, currentFingerCount); +#endif + // Reset state when just starting. + if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) { + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = 0; + } + + // Switch pointers if needed. + // Find the fastest pointer and follow it. + if (activeTouchId >= 0 && currentFingerCount > 1) { + int32_t bestId = -1; + float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; + for (BitSet32 idBits(mCurrentFingerIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + float vx, vy; + if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { + float speed = hypotf(vx, vy); + if (speed > bestSpeed) { + bestId = id; + bestSpeed = speed; + } + } + } + if (bestId >= 0 && bestId != activeTouchId) { + mPointerGesture.activeTouchId = activeTouchId = bestId; + activeTouchChanged = true; +#if DEBUG_GESTURES + ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, " + "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed); +#endif + } + } + + if (activeTouchId >= 0 && mLastFingerIdBits.hasBit(activeTouchId)) { + const RawPointerData::Pointer& currentPointer = + mCurrentRawPointerData.pointerForId(activeTouchId); + const RawPointerData::Pointer& lastPointer = + mLastRawPointerData.pointerForId(activeTouchId); + float deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale; + + rotateDelta(mSurfaceOrientation, &deltaX, &deltaY); + mPointerVelocityControl.move(when, &deltaX, &deltaY); + + // Move the pointer using a relative motion. + // When using spots, the click will occur at the position of the anchor + // spot and all other spots will move there. + mPointerController->move(deltaX, deltaY); + } else { + mPointerVelocityControl.reset(); + } + + float x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + } else if (currentFingerCount == 0) { + // Case 3. No fingers down and button is not pressed. (NEUTRAL) + if (mPointerGesture.lastGestureMode != PointerGesture::NEUTRAL) { + *outFinishPreviousGesture = true; + } + + // Watch for taps coming out of HOVER or TAP_DRAG mode. + // Checking for taps after TAP_DRAG allows us to detect double-taps. + bool tapped = false; + if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER + || mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) + && lastFingerCount == 1) { + if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) { + float x, y; + mPointerController->getPosition(&x, &y); + if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop + && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { +#if DEBUG_GESTURES + ALOGD("Gestures: TAP"); +#endif + + mPointerGesture.tapUpTime = when; + getContext()->requestTimeoutAtTime(when + + mConfig.pointerGestureTapDragInterval); + + mPointerGesture.activeGestureId = 0; + mPointerGesture.currentGestureMode = PointerGesture::TAP; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit( + mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[ + mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = + mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_X, mPointerGesture.tapX); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + tapped = true; + } else { +#if DEBUG_GESTURES + ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, + y - mPointerGesture.tapY); +#endif + } + } else { +#if DEBUG_GESTURES + if (mPointerGesture.tapDownTime != LLONG_MIN) { + ALOGD("Gestures: Not a TAP, %0.3fms since down", + (when - mPointerGesture.tapDownTime) * 0.000001f); + } else { + ALOGD("Gestures: Not a TAP, incompatible mode transitions"); + } +#endif + } + } + + mPointerVelocityControl.reset(); + + if (!tapped) { +#if DEBUG_GESTURES + ALOGD("Gestures: NEUTRAL"); +#endif + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; + mPointerGesture.currentGestureIdBits.clear(); + } + } else if (currentFingerCount == 1) { + // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG) + // The pointer follows the active touch point. + // When in HOVER, emit HOVER_MOVE events at the pointer location. + // When in TAP_DRAG, emit MOVE events at the pointer location. + ALOG_ASSERT(activeTouchId >= 0); + + mPointerGesture.currentGestureMode = PointerGesture::HOVER; + if (mPointerGesture.lastGestureMode == PointerGesture::TAP) { + if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) { + float x, y; + mPointerController->getPosition(&x, &y); + if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop + && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { + mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG; + } else { +#if DEBUG_GESTURES + ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, + y - mPointerGesture.tapY); +#endif + } + } else { +#if DEBUG_GESTURES + ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up", + (when - mPointerGesture.tapUpTime) * 0.000001f); +#endif + } + } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) { + mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG; + } + + if (mLastFingerIdBits.hasBit(activeTouchId)) { + const RawPointerData::Pointer& currentPointer = + mCurrentRawPointerData.pointerForId(activeTouchId); + const RawPointerData::Pointer& lastPointer = + mLastRawPointerData.pointerForId(activeTouchId); + float deltaX = (currentPointer.x - lastPointer.x) + * mPointerXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) + * mPointerYMovementScale; + + rotateDelta(mSurfaceOrientation, &deltaX, &deltaY); + mPointerVelocityControl.move(when, &deltaX, &deltaY); + + // Move the pointer using a relative motion. + // When using spots, the hover or drag will occur at the position of the anchor spot. + mPointerController->move(deltaX, deltaY); + } else { + mPointerVelocityControl.reset(); + } + + bool down; + if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) { +#if DEBUG_GESTURES + ALOGD("Gestures: TAP_DRAG"); +#endif + down = true; + } else { +#if DEBUG_GESTURES + ALOGD("Gestures: HOVER"); +#endif + if (mPointerGesture.lastGestureMode != PointerGesture::HOVER) { + *outFinishPreviousGesture = true; + } + mPointerGesture.activeGestureId = 0; + down = false; + } + + float x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, + down ? 1.0f : 0.0f); + + if (lastFingerCount == 0 && currentFingerCount != 0) { + mPointerGesture.resetTap(); + mPointerGesture.tapDownTime = when; + mPointerGesture.tapX = x; + mPointerGesture.tapY = y; + } + } else { + // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM) + // We need to provide feedback for each finger that goes down so we cannot wait + // for the fingers to move before deciding what to do. + // + // The ambiguous case is deciding what to do when there are two fingers down but they + // have not moved enough to determine whether they are part of a drag or part of a + // freeform gesture, or just a press or long-press at the pointer location. + // + // When there are two fingers we start with the PRESS hypothesis and we generate a + // down at the pointer location. + // + // When the two fingers move enough or when additional fingers are added, we make + // a decision to transition into SWIPE or FREEFORM mode accordingly. + ALOG_ASSERT(activeTouchId >= 0); + + bool settled = when >= mPointerGesture.firstTouchTime + + mConfig.pointerGestureMultitouchSettleInterval; + if (mPointerGesture.lastGestureMode != PointerGesture::PRESS + && mPointerGesture.lastGestureMode != PointerGesture::SWIPE + && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + *outFinishPreviousGesture = true; + } else if (!settled && currentFingerCount > lastFingerCount) { + // Additional pointers have gone down but not yet settled. + // Reset the gesture. +#if DEBUG_GESTURES + ALOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, " + "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime + + mConfig.pointerGestureMultitouchSettleInterval - when) + * 0.000001f); +#endif + *outCancelPreviousGesture = true; + } else { + // Continue previous gesture. + mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + } + + if (*outFinishPreviousGesture || *outCancelPreviousGesture) { + mPointerGesture.currentGestureMode = PointerGesture::PRESS; + mPointerGesture.activeGestureId = 0; + mPointerGesture.referenceIdBits.clear(); + mPointerVelocityControl.reset(); + + // Use the centroid and pointer location as the reference points for the gesture. +#if DEBUG_GESTURES + ALOGD("Gestures: Using centroid as reference for MULTITOUCH, " + "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime + + mConfig.pointerGestureMultitouchSettleInterval - when) + * 0.000001f); +#endif + mCurrentRawPointerData.getCentroidOfTouchingPointers( + &mPointerGesture.referenceTouchX, + &mPointerGesture.referenceTouchY); + mPointerController->getPosition(&mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); + } + + // Clear the reference deltas for fingers not yet included in the reference calculation. + for (BitSet32 idBits(mCurrentFingerIdBits.value + & ~mPointerGesture.referenceIdBits.value); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + mPointerGesture.referenceDeltas[id].dx = 0; + mPointerGesture.referenceDeltas[id].dy = 0; + } + mPointerGesture.referenceIdBits = mCurrentFingerIdBits; + + // Add delta for all fingers and calculate a common movement delta. + float commonDeltaX = 0, commonDeltaY = 0; + BitSet32 commonIdBits(mLastFingerIdBits.value + & mCurrentFingerIdBits.value); + for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) { + bool first = (idBits == commonIdBits); + uint32_t id = idBits.clearFirstMarkedBit(); + const RawPointerData::Pointer& cpd = mCurrentRawPointerData.pointerForId(id); + const RawPointerData::Pointer& lpd = mLastRawPointerData.pointerForId(id); + PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; + delta.dx += cpd.x - lpd.x; + delta.dy += cpd.y - lpd.y; + + if (first) { + commonDeltaX = delta.dx; + commonDeltaY = delta.dy; + } else { + commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx); + commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy); + } + } + + // Consider transitions from PRESS to SWIPE or MULTITOUCH. + if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) { + float dist[MAX_POINTER_ID + 1]; + int32_t distOverThreshold = 0; + for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; + dist[id] = hypotf(delta.dx * mPointerXZoomScale, + delta.dy * mPointerYZoomScale); + if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) { + distOverThreshold += 1; + } + } + + // Only transition when at least two pointers have moved further than + // the minimum distance threshold. + if (distOverThreshold >= 2) { + if (currentFingerCount > 2) { + // There are more than two pointers, switch to FREEFORM. +#if DEBUG_GESTURES + ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } else { + // There are exactly two pointers. + BitSet32 idBits(mCurrentFingerIdBits); + uint32_t id1 = idBits.clearFirstMarkedBit(); + uint32_t id2 = idBits.firstMarkedBit(); + const RawPointerData::Pointer& p1 = mCurrentRawPointerData.pointerForId(id1); + const RawPointerData::Pointer& p2 = mCurrentRawPointerData.pointerForId(id2); + float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y); + if (mutualDistance > mPointerGestureMaxSwipeWidth) { + // There are two pointers but they are too far apart for a SWIPE, + // switch to FREEFORM. +#if DEBUG_GESTURES + ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f", + mutualDistance, mPointerGestureMaxSwipeWidth); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } else { + // There are two pointers. Wait for both pointers to start moving + // before deciding whether this is a SWIPE or FREEFORM gesture. + float dist1 = dist[id1]; + float dist2 = dist[id2]; + if (dist1 >= mConfig.pointerGestureMultitouchMinDistance + && dist2 >= mConfig.pointerGestureMultitouchMinDistance) { + // Calculate the dot product of the displacement vectors. + // When the vectors are oriented in approximately the same direction, + // the angle betweeen them is near zero and the cosine of the angle + // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2). + PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1]; + PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2]; + float dx1 = delta1.dx * mPointerXZoomScale; + float dy1 = delta1.dy * mPointerYZoomScale; + float dx2 = delta2.dx * mPointerXZoomScale; + float dy2 = delta2.dy * mPointerYZoomScale; + float dot = dx1 * dx2 + dy1 * dy2; + float cosine = dot / (dist1 * dist2); // denominator always > 0 + if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) { + // Pointers are moving in the same direction. Switch to SWIPE. +#if DEBUG_GESTURES + ALOGD("Gestures: PRESS transitioned to SWIPE, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f >= %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, + dist2, mConfig.pointerGestureMultitouchMinDistance, + cosine, mConfig.pointerGestureSwipeTransitionAngleCosine); +#endif + mPointerGesture.currentGestureMode = PointerGesture::SWIPE; + } else { + // Pointers are moving in different directions. Switch to FREEFORM. +#if DEBUG_GESTURES + ALOGD("Gestures: PRESS transitioned to FREEFORM, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f < %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, + dist2, mConfig.pointerGestureMultitouchMinDistance, + cosine, mConfig.pointerGestureSwipeTransitionAngleCosine); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } + } + } + } + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // Switch from SWIPE to FREEFORM if additional pointers go down. + // Cancel previous gesture. + if (currentFingerCount > 2) { +#if DEBUG_GESTURES + ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } + } + + // Move the reference points based on the overall group motion of the fingers + // except in PRESS mode while waiting for a transition to occur. + if (mPointerGesture.currentGestureMode != PointerGesture::PRESS + && (commonDeltaX || commonDeltaY)) { + for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; + delta.dx = 0; + delta.dy = 0; + } + + mPointerGesture.referenceTouchX += commonDeltaX; + mPointerGesture.referenceTouchY += commonDeltaY; + + commonDeltaX *= mPointerXMovementScale; + commonDeltaY *= mPointerYMovementScale; + + rotateDelta(mSurfaceOrientation, &commonDeltaX, &commonDeltaY); + mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY); + + mPointerGesture.referenceGestureX += commonDeltaX; + mPointerGesture.referenceGestureY += commonDeltaY; + } + + // Report gestures. + if (mPointerGesture.currentGestureMode == PointerGesture::PRESS + || mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // PRESS or SWIPE mode. +#if DEBUG_GESTURES + ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); +#endif + ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) { + // FREEFORM mode. +#if DEBUG_GESTURES + ALOGD("Gestures: FREEFORM activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); +#endif + ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + + BitSet32 mappedTouchIdBits; + BitSet32 usedGestureIdBits; + if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + // Initially, assign the active gesture id to the active touch point + // if there is one. No other touch id bits are mapped yet. + if (!*outCancelPreviousGesture) { + mappedTouchIdBits.markBit(activeTouchId); + usedGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = + mPointerGesture.activeGestureId; + } else { + mPointerGesture.activeGestureId = -1; + } + } else { + // Otherwise, assume we mapped all touches from the previous frame. + // Reuse all mappings that are still applicable. + mappedTouchIdBits.value = mLastFingerIdBits.value + & mCurrentFingerIdBits.value; + usedGestureIdBits = mPointerGesture.lastGestureIdBits; + + // Check whether we need to choose a new active gesture id because the + // current went went up. + for (BitSet32 upTouchIdBits(mLastFingerIdBits.value + & ~mCurrentFingerIdBits.value); + !upTouchIdBits.isEmpty(); ) { + uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit(); + uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId]; + if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) { + mPointerGesture.activeGestureId = -1; + break; + } + } + } + +#if DEBUG_GESTURES + ALOGD("Gestures: FREEFORM follow up " + "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " + "activeGestureId=%d", + mappedTouchIdBits.value, usedGestureIdBits.value, + mPointerGesture.activeGestureId); +#endif + + BitSet32 idBits(mCurrentFingerIdBits); + for (uint32_t i = 0; i < currentFingerCount; i++) { + uint32_t touchId = idBits.clearFirstMarkedBit(); + uint32_t gestureId; + if (!mappedTouchIdBits.hasBit(touchId)) { + gestureId = usedGestureIdBits.markFirstUnmarkedBit(); + mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; +#if DEBUG_GESTURES + ALOGD("Gestures: FREEFORM " + "new mapping for touch id %d -> gesture id %d", + touchId, gestureId); +#endif + } else { + gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; +#if DEBUG_GESTURES + ALOGD("Gestures: FREEFORM " + "existing mapping for touch id %d -> gesture id %d", + touchId, gestureId); +#endif + } + mPointerGesture.currentGestureIdBits.markBit(gestureId); + mPointerGesture.currentGestureIdToIndex[gestureId] = i; + + const RawPointerData::Pointer& pointer = + mCurrentRawPointerData.pointerForId(touchId); + float deltaX = (pointer.x - mPointerGesture.referenceTouchX) + * mPointerXZoomScale; + float deltaY = (pointer.y - mPointerGesture.referenceTouchY) + * mPointerYZoomScale; + rotateDelta(mSurfaceOrientation, &deltaX, &deltaY); + + mPointerGesture.currentGestureProperties[i].clear(); + mPointerGesture.currentGestureProperties[i].id = gestureId; + mPointerGesture.currentGestureProperties[i].toolType = + AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureCoords[i].clear(); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_X, mPointerGesture.referenceGestureX + deltaX); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_Y, mPointerGesture.referenceGestureY + deltaY); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + } + + if (mPointerGesture.activeGestureId < 0) { + mPointerGesture.activeGestureId = + mPointerGesture.currentGestureIdBits.firstMarkedBit(); +#if DEBUG_GESTURES + ALOGD("Gestures: FREEFORM new " + "activeGestureId=%d", mPointerGesture.activeGestureId); +#endif + } + } + } + + mPointerController->setButtonState(mCurrentButtonState); + +#if DEBUG_GESTURES + ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " + "currentGestureMode=%d, currentGestureIdBits=0x%08x, " + "lastGestureMode=%d, lastGestureIdBits=0x%08x", + toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), + mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value, + mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value); + for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.currentGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; + ALOGD(" currentGesture[%d]: index=%d, toolType=%d, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, properties.toolType, + coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } + for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.lastGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; + ALOGD(" lastGesture[%d]: index=%d, toolType=%d, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, properties.toolType, + coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } +#endif + return true; +} + +void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) { + mPointerSimple.currentCoords.clear(); + mPointerSimple.currentProperties.clear(); + + bool down, hovering; + if (!mCurrentStylusIdBits.isEmpty()) { + uint32_t id = mCurrentStylusIdBits.firstMarkedBit(); + uint32_t index = mCurrentCookedPointerData.idToIndex[id]; + float x = mCurrentCookedPointerData.pointerCoords[index].getX(); + float y = mCurrentCookedPointerData.pointerCoords[index].getY(); + mPointerController->setPosition(x, y); + + hovering = mCurrentCookedPointerData.hoveringIdBits.hasBit(id); + down = !hovering; + + mPointerController->getPosition(&x, &y); + mPointerSimple.currentCoords.copyFrom(mCurrentCookedPointerData.pointerCoords[index]); + mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerSimple.currentProperties.id = 0; + mPointerSimple.currentProperties.toolType = + mCurrentCookedPointerData.pointerProperties[index].toolType; + } else { + down = false; + hovering = false; + } + + dispatchPointerSimple(when, policyFlags, down, hovering); +} + +void TouchInputMapper::abortPointerStylus(nsecs_t when, uint32_t policyFlags) { + abortPointerSimple(when, policyFlags); +} + +void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) { + mPointerSimple.currentCoords.clear(); + mPointerSimple.currentProperties.clear(); + + bool down, hovering; + if (!mCurrentMouseIdBits.isEmpty()) { + uint32_t id = mCurrentMouseIdBits.firstMarkedBit(); + uint32_t currentIndex = mCurrentRawPointerData.idToIndex[id]; + if (mLastMouseIdBits.hasBit(id)) { + uint32_t lastIndex = mCurrentRawPointerData.idToIndex[id]; + float deltaX = (mCurrentRawPointerData.pointers[currentIndex].x + - mLastRawPointerData.pointers[lastIndex].x) + * mPointerXMovementScale; + float deltaY = (mCurrentRawPointerData.pointers[currentIndex].y + - mLastRawPointerData.pointers[lastIndex].y) + * mPointerYMovementScale; + + rotateDelta(mSurfaceOrientation, &deltaX, &deltaY); + mPointerVelocityControl.move(when, &deltaX, &deltaY); + + mPointerController->move(deltaX, deltaY); + } else { + mPointerVelocityControl.reset(); + } + + down = isPointerDown(mCurrentButtonState); + hovering = !down; + + float x, y; + mPointerController->getPosition(&x, &y); + mPointerSimple.currentCoords.copyFrom( + mCurrentCookedPointerData.pointerCoords[currentIndex]); + mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, + hovering ? 0.0f : 1.0f); + mPointerSimple.currentProperties.id = 0; + mPointerSimple.currentProperties.toolType = + mCurrentCookedPointerData.pointerProperties[currentIndex].toolType; + } else { + mPointerVelocityControl.reset(); + + down = false; + hovering = false; + } + + dispatchPointerSimple(when, policyFlags, down, hovering); +} + +void TouchInputMapper::abortPointerMouse(nsecs_t when, uint32_t policyFlags) { + abortPointerSimple(when, policyFlags); + + mPointerVelocityControl.reset(); +} + +void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, + bool down, bool hovering) { + int32_t metaState = getContext()->getGlobalMetaState(); + + if (mPointerController != NULL) { + if (down || hovering) { + mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER); + mPointerController->clearSpots(); + mPointerController->setButtonState(mCurrentButtonState); + mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE); + } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + } + } + + if (mPointerSimple.down && !down) { + mPointerSimple.down = false; + + // Send up. + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_UP, 0, metaState, mLastButtonState, 0, + mViewport.displayId, + 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.downTime); + getListener()->notifyMotion(&args); + } + + if (mPointerSimple.hovering && !hovering) { + mPointerSimple.hovering = false; + + // Send hover exit. + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0, + mViewport.displayId, + 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.downTime); + getListener()->notifyMotion(&args); + } + + if (down) { + if (!mPointerSimple.down) { + mPointerSimple.down = true; + mPointerSimple.downTime = when; + + // Send down. + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_DOWN, 0, metaState, mCurrentButtonState, 0, + mViewport.displayId, + 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.downTime); + getListener()->notifyMotion(&args); + } + + // Send move. + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, mCurrentButtonState, 0, + mViewport.displayId, + 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.downTime); + getListener()->notifyMotion(&args); + } + + if (hovering) { + if (!mPointerSimple.hovering) { + mPointerSimple.hovering = true; + + // Send hover enter. + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0, + mViewport.displayId, + 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.downTime); + getListener()->notifyMotion(&args); + } + + // Send hover move. + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0, + mViewport.displayId, + 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.downTime); + getListener()->notifyMotion(&args); + } + + if (mCurrentRawVScroll || mCurrentRawHScroll) { + float vscroll = mCurrentRawVScroll; + float hscroll = mCurrentRawHScroll; + mWheelYVelocityControl.move(when, NULL, &vscroll); + mWheelXVelocityControl.move(when, &hscroll, NULL); + + // Send scroll. + PointerCoords pointerCoords; + pointerCoords.copyFrom(mPointerSimple.currentCoords); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); + + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_SCROLL, 0, metaState, mCurrentButtonState, 0, + mViewport.displayId, + 1, &mPointerSimple.currentProperties, &pointerCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.downTime); + getListener()->notifyMotion(&args); + } + + // Save state. + if (down || hovering) { + mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords); + mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties); + } else { + mPointerSimple.reset(); + } +} + +void TouchInputMapper::abortPointerSimple(nsecs_t when, uint32_t policyFlags) { + mPointerSimple.currentCoords.clear(); + mPointerSimple.currentProperties.clear(); + + dispatchPointerSimple(when, policyFlags, false, false); +} + +void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, + int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, + const PointerProperties* properties, const PointerCoords* coords, + const uint32_t* idToIndex, BitSet32 idBits, + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) { + PointerCoords pointerCoords[MAX_POINTERS]; + PointerProperties pointerProperties[MAX_POINTERS]; + uint32_t pointerCount = 0; + while (!idBits.isEmpty()) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = idToIndex[id]; + pointerProperties[pointerCount].copyFrom(properties[index]); + pointerCoords[pointerCount].copyFrom(coords[index]); + + if (changedId >= 0 && id == uint32_t(changedId)) { + action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + } + + pointerCount += 1; + } + + ALOG_ASSERT(pointerCount != 0); + + if (changedId >= 0 && pointerCount == 1) { + // Replace initial down and final up action. + // We can compare the action without masking off the changed pointer index + // because we know the index is 0. + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) { + action = AMOTION_EVENT_ACTION_DOWN; + } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) { + action = AMOTION_EVENT_ACTION_UP; + } else { + // Can't happen. + ALOG_ASSERT(false); + } + } + + NotifyMotionArgs args(when, getDeviceId(), source, policyFlags, + action, flags, metaState, buttonState, edgeFlags, + mViewport.displayId, pointerCount, pointerProperties, pointerCoords, + xPrecision, yPrecision, downTime); + getListener()->notifyMotion(&args); +} + +bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties, + const PointerCoords* inCoords, const uint32_t* inIdToIndex, + PointerProperties* outProperties, PointerCoords* outCoords, const uint32_t* outIdToIndex, + BitSet32 idBits) const { + bool changed = false; + while (!idBits.isEmpty()) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t inIndex = inIdToIndex[id]; + uint32_t outIndex = outIdToIndex[id]; + + const PointerProperties& curInProperties = inProperties[inIndex]; + const PointerCoords& curInCoords = inCoords[inIndex]; + PointerProperties& curOutProperties = outProperties[outIndex]; + PointerCoords& curOutCoords = outCoords[outIndex]; + + if (curInProperties != curOutProperties) { + curOutProperties.copyFrom(curInProperties); + changed = true; + } + + if (curInCoords != curOutCoords) { + curOutCoords.copyFrom(curInCoords); + changed = true; + } + } + return changed; +} + +void TouchInputMapper::fadePointer() { + if (mPointerController != NULL) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + } +} + +bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) { + return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue + && y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue; +} + +const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit( + int32_t x, int32_t y) { + size_t numVirtualKeys = mVirtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; + +#if DEBUG_VIRTUAL_KEYS + ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " + "left=%d, top=%d, right=%d, bottom=%d", + x, y, + virtualKey.keyCode, virtualKey.scanCode, + virtualKey.hitLeft, virtualKey.hitTop, + virtualKey.hitRight, virtualKey.hitBottom); +#endif + + if (virtualKey.isHit(x, y)) { + return & virtualKey; + } + } + + return NULL; +} + +void TouchInputMapper::assignPointerIds() { + uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount; + uint32_t lastPointerCount = mLastRawPointerData.pointerCount; + + mCurrentRawPointerData.clearIdBits(); + + if (currentPointerCount == 0) { + // No pointers to assign. + return; + } + + if (lastPointerCount == 0) { + // All pointers are new. + for (uint32_t i = 0; i < currentPointerCount; i++) { + uint32_t id = i; + mCurrentRawPointerData.pointers[i].id = id; + mCurrentRawPointerData.idToIndex[id] = i; + mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(i)); + } + return; + } + + if (currentPointerCount == 1 && lastPointerCount == 1 + && mCurrentRawPointerData.pointers[0].toolType + == mLastRawPointerData.pointers[0].toolType) { + // Only one pointer and no change in count so it must have the same id as before. + uint32_t id = mLastRawPointerData.pointers[0].id; + mCurrentRawPointerData.pointers[0].id = id; + mCurrentRawPointerData.idToIndex[id] = 0; + mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(0)); + return; + } + + // General case. + // We build a heap of squared euclidean distances between current and last pointers + // associated with the current and last pointer indices. Then, we find the best + // match (by distance) for each current pointer. + // The pointers must have the same tool type but it is possible for them to + // transition from hovering to touching or vice-versa while retaining the same id. + PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS]; + + uint32_t heapSize = 0; + for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; + currentPointerIndex++) { + for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount; + lastPointerIndex++) { + const RawPointerData::Pointer& currentPointer = + mCurrentRawPointerData.pointers[currentPointerIndex]; + const RawPointerData::Pointer& lastPointer = + mLastRawPointerData.pointers[lastPointerIndex]; + if (currentPointer.toolType == lastPointer.toolType) { + int64_t deltaX = currentPointer.x - lastPointer.x; + int64_t deltaY = currentPointer.y - lastPointer.y; + + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + + // Insert new element into the heap (sift up). + heap[heapSize].currentPointerIndex = currentPointerIndex; + heap[heapSize].lastPointerIndex = lastPointerIndex; + heap[heapSize].distance = distance; + heapSize += 1; + } + } + } + + // Heapify + for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) { + startIndex -= 1; + for (uint32_t parentIndex = startIndex; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + } + +#if DEBUG_POINTER_ASSIGNMENT + ALOGD("assignPointerIds - initial distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + ALOGD(" heap[%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); heapSize > 0 && i > 0; i--) { + while (heapSize > 0) { + if (first) { + // The first time through the loop, we just consume the root element of + // the heap (the one with smallest distance). + first = false; + } else { + // Previous iterations consumed the root element of the heap. + // Pop root element off of the heap (sift down). + heap[0] = heap[heapSize]; + for (uint32_t parentIndex = 0; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + +#if DEBUG_POINTER_ASSIGNMENT + ALOGD("assignPointerIds - reduced distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + ALOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif + } + + heapSize -= 1; + + uint32_t currentPointerIndex = heap[0].currentPointerIndex; + if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched + + uint32_t lastPointerIndex = heap[0].lastPointerIndex; + if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched + + matchedCurrentBits.markBit(currentPointerIndex); + matchedLastBits.markBit(lastPointerIndex); + + uint32_t id = mLastRawPointerData.pointers[lastPointerIndex].id; + mCurrentRawPointerData.pointers[currentPointerIndex].id = id; + mCurrentRawPointerData.idToIndex[id] = currentPointerIndex; + mCurrentRawPointerData.markIdBit(id, + mCurrentRawPointerData.isHovering(currentPointerIndex)); + usedIdBits.markBit(id); + +#if DEBUG_POINTER_ASSIGNMENT + ALOGD("assignPointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld", + lastPointerIndex, currentPointerIndex, id, heap[0].distance); +#endif + break; + } + } + + // Assign fresh ids to pointers that were not matched in the process. + for (uint32_t i = currentPointerCount - matchedCurrentBits.count(); i != 0; i--) { + uint32_t currentPointerIndex = matchedCurrentBits.markFirstUnmarkedBit(); + uint32_t id = usedIdBits.markFirstUnmarkedBit(); + + mCurrentRawPointerData.pointers[currentPointerIndex].id = id; + mCurrentRawPointerData.idToIndex[id] = currentPointerIndex; + mCurrentRawPointerData.markIdBit(id, + mCurrentRawPointerData.isHovering(currentPointerIndex)); + +#if DEBUG_POINTER_ASSIGNMENT + ALOGD("assignPointerIds - assigned: cur=%d, id=%d", + currentPointerIndex, id); +#endif + } +} + +int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + if (mCurrentVirtualKey.down && mCurrentVirtualKey.keyCode == keyCode) { + return AKEY_STATE_VIRTUAL; + } + + size_t numVirtualKeys = mVirtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; + if (virtualKey.keyCode == keyCode) { + return AKEY_STATE_UP; + } + } + + return AKEY_STATE_UNKNOWN; +} + +int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + if (mCurrentVirtualKey.down && mCurrentVirtualKey.scanCode == scanCode) { + return AKEY_STATE_VIRTUAL; + } + + size_t numVirtualKeys = mVirtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; + if (virtualKey.scanCode == scanCode) { + return AKEY_STATE_UP; + } + } + + return AKEY_STATE_UNKNOWN; +} + +bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + size_t numVirtualKeys = mVirtualKeys.size(); + for (size_t i = 0; i < numVirtualKeys; i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; + + for (size_t i = 0; i < numCodes; i++) { + if (virtualKey.keyCode == keyCodes[i]) { + outFlags[i] = 1; + } + } + } + + return true; +} + + +// --- SingleTouchInputMapper --- + +SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) : + TouchInputMapper(device) { +} + +SingleTouchInputMapper::~SingleTouchInputMapper() { +} + +void SingleTouchInputMapper::reset(nsecs_t when) { + mSingleTouchMotionAccumulator.reset(getDevice()); + + TouchInputMapper::reset(when); +} + +void SingleTouchInputMapper::process(const RawEvent* rawEvent) { + TouchInputMapper::process(rawEvent); + + mSingleTouchMotionAccumulator.process(rawEvent); +} + +void SingleTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) { + if (mTouchButtonAccumulator.isToolActive()) { + mCurrentRawPointerData.pointerCount = 1; + mCurrentRawPointerData.idToIndex[0] = 0; + + bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE + && (mTouchButtonAccumulator.isHovering() + || (mRawPointerAxes.pressure.valid + && mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0)); + mCurrentRawPointerData.markIdBit(0, isHovering); + + RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[0]; + outPointer.id = 0; + outPointer.x = mSingleTouchMotionAccumulator.getAbsoluteX(); + outPointer.y = mSingleTouchMotionAccumulator.getAbsoluteY(); + outPointer.pressure = mSingleTouchMotionAccumulator.getAbsolutePressure(); + outPointer.touchMajor = 0; + outPointer.touchMinor = 0; + outPointer.toolMajor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth(); + outPointer.toolMinor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth(); + outPointer.orientation = 0; + outPointer.distance = mSingleTouchMotionAccumulator.getAbsoluteDistance(); + outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX(); + outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY(); + outPointer.toolType = mTouchButtonAccumulator.getToolType(); + if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + } + outPointer.isHovering = isHovering; + } +} + +void SingleTouchInputMapper::configureRawPointerAxes() { + TouchInputMapper::configureRawPointerAxes(); + + getAbsoluteAxisInfo(ABS_X, &mRawPointerAxes.x); + getAbsoluteAxisInfo(ABS_Y, &mRawPointerAxes.y); + getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPointerAxes.pressure); + getAbsoluteAxisInfo(ABS_TOOL_WIDTH, &mRawPointerAxes.toolMajor); + getAbsoluteAxisInfo(ABS_DISTANCE, &mRawPointerAxes.distance); + getAbsoluteAxisInfo(ABS_TILT_X, &mRawPointerAxes.tiltX); + getAbsoluteAxisInfo(ABS_TILT_Y, &mRawPointerAxes.tiltY); +} + +bool SingleTouchInputMapper::hasStylus() const { + return mTouchButtonAccumulator.hasStylus(); +} + + +// --- MultiTouchInputMapper --- + +MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) : + TouchInputMapper(device) { +} + +MultiTouchInputMapper::~MultiTouchInputMapper() { +} + +void MultiTouchInputMapper::reset(nsecs_t when) { + mMultiTouchMotionAccumulator.reset(getDevice()); + + mPointerIdBits.clear(); + + TouchInputMapper::reset(when); +} + +void MultiTouchInputMapper::process(const RawEvent* rawEvent) { + TouchInputMapper::process(rawEvent); + + mMultiTouchMotionAccumulator.process(rawEvent); +} + +void MultiTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) { + size_t inCount = mMultiTouchMotionAccumulator.getSlotCount(); + size_t outCount = 0; + BitSet32 newPointerIdBits; + + for (size_t inIndex = 0; inIndex < inCount; inIndex++) { + const MultiTouchMotionAccumulator::Slot* inSlot = + mMultiTouchMotionAccumulator.getSlot(inIndex); + if (!inSlot->isInUse()) { + continue; + } + + if (outCount >= MAX_POINTERS) { +#if DEBUG_POINTERS + ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; " + "ignoring the rest.", + getDeviceName().string(), MAX_POINTERS); +#endif + break; // too many fingers! + } + + RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[outCount]; + outPointer.x = inSlot->getX(); + outPointer.y = inSlot->getY(); + outPointer.pressure = inSlot->getPressure(); + outPointer.touchMajor = inSlot->getTouchMajor(); + outPointer.touchMinor = inSlot->getTouchMinor(); + outPointer.toolMajor = inSlot->getToolMajor(); + outPointer.toolMinor = inSlot->getToolMinor(); + outPointer.orientation = inSlot->getOrientation(); + outPointer.distance = inSlot->getDistance(); + outPointer.tiltX = 0; + outPointer.tiltY = 0; + + outPointer.toolType = inSlot->getToolType(); + if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + outPointer.toolType = mTouchButtonAccumulator.getToolType(); + if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + } + } + + bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE + && (mTouchButtonAccumulator.isHovering() + || (mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0)); + outPointer.isHovering = isHovering; + + // Assign pointer id using tracking id if available. + if (*outHavePointerIds) { + int32_t trackingId = inSlot->getTrackingId(); + int32_t id = -1; + if (trackingId >= 0) { + for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty(); ) { + uint32_t n = idBits.clearFirstMarkedBit(); + if (mPointerTrackingIdMap[n] == trackingId) { + id = n; + } + } + + if (id < 0 && !mPointerIdBits.isFull()) { + id = mPointerIdBits.markFirstUnmarkedBit(); + mPointerTrackingIdMap[id] = trackingId; + } + } + if (id < 0) { + *outHavePointerIds = false; + mCurrentRawPointerData.clearIdBits(); + newPointerIdBits.clear(); + } else { + outPointer.id = id; + mCurrentRawPointerData.idToIndex[id] = outCount; + mCurrentRawPointerData.markIdBit(id, isHovering); + newPointerIdBits.markBit(id); + } + } + + outCount += 1; + } + + mCurrentRawPointerData.pointerCount = outCount; + mPointerIdBits = newPointerIdBits; + + mMultiTouchMotionAccumulator.finishSync(); +} + +void MultiTouchInputMapper::configureRawPointerAxes() { + TouchInputMapper::configureRawPointerAxes(); + + getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x); + getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y); + getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor); + getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor); + getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor); + getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor); + getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation); + getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure); + getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance); + getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId); + getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot); + + if (mRawPointerAxes.trackingId.valid + && mRawPointerAxes.slot.valid + && mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) { + size_t slotCount = mRawPointerAxes.slot.maxValue + 1; + if (slotCount > MAX_SLOTS) { + ALOGW("MultiTouch Device %s reported %d slots but the framework " + "only supports a maximum of %d slots at this time.", + getDeviceName().string(), slotCount, MAX_SLOTS); + slotCount = MAX_SLOTS; + } + mMultiTouchMotionAccumulator.configure(getDevice(), + slotCount, true /*usingSlotsProtocol*/); + } else { + mMultiTouchMotionAccumulator.configure(getDevice(), + MAX_POINTERS, false /*usingSlotsProtocol*/); + } +} + +bool MultiTouchInputMapper::hasStylus() const { + return mMultiTouchMotionAccumulator.hasStylus() + || mTouchButtonAccumulator.hasStylus(); +} + + +// --- JoystickInputMapper --- + +JoystickInputMapper::JoystickInputMapper(InputDevice* device) : + InputMapper(device) { +} + +JoystickInputMapper::~JoystickInputMapper() { +} + +uint32_t JoystickInputMapper::getSources() { + return AINPUT_SOURCE_JOYSTICK; +} + +void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + for (size_t i = 0; i < mAxes.size(); i++) { + const Axis& axis = mAxes.valueAt(i); + addMotionRange(axis.axisInfo.axis, axis, info); + + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + addMotionRange(axis.axisInfo.highAxis, axis, info); + + } + } +} + +void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, + InputDeviceInfo* info) { + info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, + axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution); + /* In order to ease the transition for developers from using the old axes + * to the newer, more semantically correct axes, we'll continue to register + * the old axes as duplicates of their corresponding new ones. */ + int32_t compatAxis = getCompatAxis(axisId); + if (compatAxis >= 0) { + info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, + axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution); + } +} + +/* A mapping from axes the joystick actually has to the axes that should be + * artificially created for compatibility purposes. + * Returns -1 if no compatibility axis is needed. */ +int32_t JoystickInputMapper::getCompatAxis(int32_t axis) { + switch(axis) { + case AMOTION_EVENT_AXIS_LTRIGGER: + return AMOTION_EVENT_AXIS_BRAKE; + case AMOTION_EVENT_AXIS_RTRIGGER: + return AMOTION_EVENT_AXIS_GAS; + } + return -1; +} + +void JoystickInputMapper::dump(String8& dump) { + dump.append(INDENT2 "Joystick Input Mapper:\n"); + + dump.append(INDENT3 "Axes:\n"); + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + const Axis& axis = mAxes.valueAt(i); + const char* label = getAxisLabel(axis.axisInfo.axis); + if (label) { + dump.appendFormat(INDENT4 "%s", label); + } else { + dump.appendFormat(INDENT4 "%d", axis.axisInfo.axis); + } + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + label = getAxisLabel(axis.axisInfo.highAxis); + if (label) { + dump.appendFormat(" / %s (split at %d)", label, axis.axisInfo.splitValue); + } else { + dump.appendFormat(" / %d (split at %d)", axis.axisInfo.highAxis, + axis.axisInfo.splitValue); + } + } else if (axis.axisInfo.mode == AxisInfo::MODE_INVERT) { + dump.append(" (invert)"); + } + + dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f, resolution=%0.5f\n", + axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution); + dump.appendFormat(INDENT4 " scale=%0.5f, offset=%0.5f, " + "highScale=%0.5f, highOffset=%0.5f\n", + axis.scale, axis.offset, axis.highScale, axis.highOffset); + dump.appendFormat(INDENT4 " rawAxis=%d, rawMin=%d, rawMax=%d, " + "rawFlat=%d, rawFuzz=%d, rawResolution=%d\n", + mAxes.keyAt(i), axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue, + axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz, axis.rawAxisInfo.resolution); + } +} + +void JoystickInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, uint32_t changes) { + InputMapper::configure(when, config, changes); + + if (!changes) { // first time only + // Collect all axes. + for (int32_t abs = 0; abs <= ABS_MAX; abs++) { + if (!(getAbsAxisUsage(abs, getDevice()->getClasses()) + & INPUT_DEVICE_CLASS_JOYSTICK)) { + continue; // axis must be claimed by a different device + } + + RawAbsoluteAxisInfo rawAxisInfo; + getAbsoluteAxisInfo(abs, &rawAxisInfo); + if (rawAxisInfo.valid) { + // Map axis. + AxisInfo axisInfo; + bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisInfo); + if (!explicitlyMapped) { + // Axis is not explicitly mapped, will choose a generic axis later. + axisInfo.mode = AxisInfo::MODE_NORMAL; + axisInfo.axis = -1; + } + + // Apply flat override. + int32_t rawFlat = axisInfo.flatOverride < 0 + ? rawAxisInfo.flat : axisInfo.flatOverride; + + // Calculate scaling factors and limits. + Axis axis; + if (axisInfo.mode == AxisInfo::MODE_SPLIT) { + float scale = 1.0f / (axisInfo.splitValue - rawAxisInfo.minValue); + float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue); + axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, + scale, 0.0f, highScale, 0.0f, + 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale, + rawAxisInfo.resolution * scale); + } else if (isCenteredAxis(axisInfo.axis)) { + float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue); + float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale; + axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, + scale, offset, scale, offset, + -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale, + rawAxisInfo.resolution * scale); + } else { + float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue); + axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped, + scale, 0.0f, scale, 0.0f, + 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale, + rawAxisInfo.resolution * scale); + } + + // To eliminate noise while the joystick is at rest, filter out small variations + // in axis values up front. + axis.filter = axis.fuzz ? axis.fuzz : axis.flat * 0.25f; + + mAxes.add(abs, axis); + } + } + + // If there are too many axes, start dropping them. + // Prefer to keep explicitly mapped axes. + if (mAxes.size() > PointerCoords::MAX_AXES) { + ALOGI("Joystick '%s' has %d axes but the framework only supports a maximum of %d.", + getDeviceName().string(), mAxes.size(), PointerCoords::MAX_AXES); + pruneAxes(true); + pruneAxes(false); + } + + // Assign generic axis ids to remaining axes. + int32_t nextGenericAxisId = AMOTION_EVENT_AXIS_GENERIC_1; + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + Axis& axis = mAxes.editValueAt(i); + if (axis.axisInfo.axis < 0) { + while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16 + && haveAxis(nextGenericAxisId)) { + nextGenericAxisId += 1; + } + + if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) { + axis.axisInfo.axis = nextGenericAxisId; + nextGenericAxisId += 1; + } else { + ALOGI("Ignoring joystick '%s' axis %d because all of the generic axis ids " + "have already been assigned to other axes.", + getDeviceName().string(), mAxes.keyAt(i)); + mAxes.removeItemsAt(i--); + numAxes -= 1; + } + } + } + } +} + +bool JoystickInputMapper::haveAxis(int32_t axisId) { + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + const Axis& axis = mAxes.valueAt(i); + if (axis.axisInfo.axis == axisId + || (axis.axisInfo.mode == AxisInfo::MODE_SPLIT + && axis.axisInfo.highAxis == axisId)) { + return true; + } + } + return false; +} + +void JoystickInputMapper::pruneAxes(bool ignoreExplicitlyMappedAxes) { + size_t i = mAxes.size(); + while (mAxes.size() > PointerCoords::MAX_AXES && i-- > 0) { + if (ignoreExplicitlyMappedAxes && mAxes.valueAt(i).explicitlyMapped) { + continue; + } + ALOGI("Discarding joystick '%s' axis %d because there are too many axes.", + getDeviceName().string(), mAxes.keyAt(i)); + mAxes.removeItemsAt(i); + } +} + +bool JoystickInputMapper::isCenteredAxis(int32_t axis) { + switch (axis) { + case AMOTION_EVENT_AXIS_X: + case AMOTION_EVENT_AXIS_Y: + case AMOTION_EVENT_AXIS_Z: + case AMOTION_EVENT_AXIS_RX: + case AMOTION_EVENT_AXIS_RY: + case AMOTION_EVENT_AXIS_RZ: + case AMOTION_EVENT_AXIS_HAT_X: + case AMOTION_EVENT_AXIS_HAT_Y: + case AMOTION_EVENT_AXIS_ORIENTATION: + case AMOTION_EVENT_AXIS_RUDDER: + case AMOTION_EVENT_AXIS_WHEEL: + return true; + default: + return false; + } +} + +void JoystickInputMapper::reset(nsecs_t when) { + // Recenter all axes. + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + Axis& axis = mAxes.editValueAt(i); + axis.resetValue(); + } + + InputMapper::reset(when); +} + +void JoystickInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_ABS: { + ssize_t index = mAxes.indexOfKey(rawEvent->code); + if (index >= 0) { + Axis& axis = mAxes.editValueAt(index); + float newValue, highNewValue; + switch (axis.axisInfo.mode) { + case AxisInfo::MODE_INVERT: + newValue = (axis.rawAxisInfo.maxValue - rawEvent->value) + * axis.scale + axis.offset; + highNewValue = 0.0f; + break; + case AxisInfo::MODE_SPLIT: + if (rawEvent->value < axis.axisInfo.splitValue) { + newValue = (axis.axisInfo.splitValue - rawEvent->value) + * axis.scale + axis.offset; + highNewValue = 0.0f; + } else if (rawEvent->value > axis.axisInfo.splitValue) { + newValue = 0.0f; + highNewValue = (rawEvent->value - axis.axisInfo.splitValue) + * axis.highScale + axis.highOffset; + } else { + newValue = 0.0f; + highNewValue = 0.0f; + } + break; + default: + newValue = rawEvent->value * axis.scale + axis.offset; + highNewValue = 0.0f; + break; + } + axis.newValue = newValue; + axis.highNewValue = highNewValue; + } + break; + } + + case EV_SYN: + switch (rawEvent->code) { + case SYN_REPORT: + sync(rawEvent->when, false /*force*/); + break; + } + break; + } +} + +void JoystickInputMapper::sync(nsecs_t when, bool force) { + if (!filterAxes(force)) { + return; + } + + int32_t metaState = mContext->getGlobalMetaState(); + int32_t buttonState = 0; + + PointerProperties pointerProperties; + pointerProperties.clear(); + pointerProperties.id = 0; + pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN; + + PointerCoords pointerCoords; + pointerCoords.clear(); + + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + const Axis& axis = mAxes.valueAt(i); + setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.axis, axis.currentValue); + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.highAxis, + axis.highCurrentValue); + } + } + + // Moving a joystick axis should not wake the device because joysticks can + // be fairly noisy even when not in use. On the other hand, pushing a gamepad + // button will likely wake the device. + // TODO: Use the input device configuration to control this behavior more finely. + uint32_t policyFlags = 0; + + NotifyMotionArgs args(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, policyFlags, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + ADISPLAY_ID_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, 0); + getListener()->notifyMotion(&args); +} + +void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords, + int32_t axis, float value) { + pointerCoords->setAxisValue(axis, value); + /* In order to ease the transition for developers from using the old axes + * to the newer, more semantically correct axes, we'll continue to produce + * values for the old axes as mirrors of the value of their corresponding + * new axes. */ + int32_t compatAxis = getCompatAxis(axis); + if (compatAxis >= 0) { + pointerCoords->setAxisValue(compatAxis, value); + } +} + +bool JoystickInputMapper::filterAxes(bool force) { + bool atLeastOneSignificantChange = force; + size_t numAxes = mAxes.size(); + for (size_t i = 0; i < numAxes; i++) { + Axis& axis = mAxes.editValueAt(i); + if (force || hasValueChangedSignificantly(axis.filter, + axis.newValue, axis.currentValue, axis.min, axis.max)) { + axis.currentValue = axis.newValue; + atLeastOneSignificantChange = true; + } + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + if (force || hasValueChangedSignificantly(axis.filter, + axis.highNewValue, axis.highCurrentValue, axis.min, axis.max)) { + axis.highCurrentValue = axis.highNewValue; + atLeastOneSignificantChange = true; + } + } + } + return atLeastOneSignificantChange; +} + +bool JoystickInputMapper::hasValueChangedSignificantly( + float filter, float newValue, float currentValue, float min, float max) { + if (newValue != currentValue) { + // Filter out small changes in value unless the value is converging on the axis + // bounds or center point. This is intended to reduce the amount of information + // sent to applications by particularly noisy joysticks (such as PS3). + if (fabs(newValue - currentValue) > filter + || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, min) + || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, max) + || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, 0)) { + return true; + } + } + return false; +} + +bool JoystickInputMapper::hasMovedNearerToValueWithinFilteredRange( + float filter, float newValue, float currentValue, float thresholdValue) { + float newDistance = fabs(newValue - thresholdValue); + if (newDistance < filter) { + float oldDistance = fabs(currentValue - thresholdValue); + if (newDistance < oldDistance) { + return true; + } + } + return false; +} + +} // namespace android diff --git a/services/inputflinger/InputReader.h b/services/inputflinger/InputReader.h new file mode 100644 index 000000000..6c4c927ee --- /dev/null +++ b/services/inputflinger/InputReader.h @@ -0,0 +1,1818 @@ +/* + * 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 "PointerControllerInterface.h" +#include "InputListener.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Maximum supported size of a vibration pattern. +// Must be at least 2. +#define MAX_VIBRATE_PATTERN_SIZE 100 + +// Maximum allowable delay value in a vibration pattern before +// which the delay will be truncated. +#define MAX_VIBRATE_PATTERN_DELAY_NSECS (1000000 * 1000000000LL) + +namespace android { + +class InputDevice; +class InputMapper; + +/* + * Describes how coordinates are mapped on a physical display. + * See com.android.server.display.DisplayViewport. + */ +struct DisplayViewport { + int32_t displayId; // -1 if invalid + int32_t orientation; + int32_t logicalLeft; + int32_t logicalTop; + int32_t logicalRight; + int32_t logicalBottom; + int32_t physicalLeft; + int32_t physicalTop; + int32_t physicalRight; + int32_t physicalBottom; + int32_t deviceWidth; + int32_t deviceHeight; + + DisplayViewport() : + displayId(ADISPLAY_ID_NONE), orientation(DISPLAY_ORIENTATION_0), + logicalLeft(0), logicalTop(0), logicalRight(0), logicalBottom(0), + physicalLeft(0), physicalTop(0), physicalRight(0), physicalBottom(0), + deviceWidth(0), deviceHeight(0) { + } + + bool operator==(const DisplayViewport& other) const { + return displayId == other.displayId + && orientation == other.orientation + && logicalLeft == other.logicalLeft + && logicalTop == other.logicalTop + && logicalRight == other.logicalRight + && logicalBottom == other.logicalBottom + && physicalLeft == other.physicalLeft + && physicalTop == other.physicalTop + && physicalRight == other.physicalRight + && physicalBottom == other.physicalBottom + && deviceWidth == other.deviceWidth + && deviceHeight == other.deviceHeight; + } + + bool operator!=(const DisplayViewport& other) const { + return !(*this == other); + } + + inline bool isValid() const { + return displayId >= 0; + } + + void setNonDisplayViewport(int32_t width, int32_t height) { + displayId = ADISPLAY_ID_NONE; + orientation = DISPLAY_ORIENTATION_0; + logicalLeft = 0; + logicalTop = 0; + logicalRight = width; + logicalBottom = height; + physicalLeft = 0; + physicalTop = 0; + physicalRight = width; + physicalBottom = height; + deviceWidth = width; + deviceHeight = height; + } +}; + +/* + * Input reader configuration. + * + * Specifies various options that modify the behavior of the input reader. + */ +struct InputReaderConfiguration { + // Describes changes that have occurred. + enum { + // The pointer speed changed. + CHANGE_POINTER_SPEED = 1 << 0, + + // The pointer gesture control changed. + CHANGE_POINTER_GESTURE_ENABLEMENT = 1 << 1, + + // The display size or orientation changed. + CHANGE_DISPLAY_INFO = 1 << 2, + + // The visible touches option changed. + CHANGE_SHOW_TOUCHES = 1 << 3, + + // The keyboard layouts must be reloaded. + CHANGE_KEYBOARD_LAYOUTS = 1 << 4, + + // The device name alias supplied by the may have changed for some devices. + CHANGE_DEVICE_ALIAS = 1 << 5, + + // All devices must be reopened. + CHANGE_MUST_REOPEN = 1 << 31, + }; + + // Gets the amount of time to disable virtual keys after the screen is touched + // in order to filter out accidental virtual key presses due to swiping gestures + // or taps near the edge of the display. May be 0 to disable the feature. + nsecs_t virtualKeyQuietTime; + + // The excluded device names for the platform. + // Devices with these names will be ignored. + Vector excludedDeviceNames; + + // Velocity control parameters for mouse pointer movements. + VelocityControlParameters pointerVelocityControlParameters; + + // Velocity control parameters for mouse wheel movements. + VelocityControlParameters wheelVelocityControlParameters; + + // True if pointer gestures are enabled. + bool pointerGesturesEnabled; + + // Quiet time between certain pointer gesture transitions. + // Time to allow for all fingers or buttons to settle into a stable state before + // starting a new gesture. + nsecs_t pointerGestureQuietInterval; + + // The minimum speed that a pointer must travel for us to consider switching the active + // touch pointer to it during a drag. This threshold is set to avoid switching due + // to noise from a finger resting on the touch pad (perhaps just pressing it down). + float pointerGestureDragMinSwitchSpeed; // in pixels per second + + // Tap gesture delay time. + // The time between down and up must be less than this to be considered a tap. + nsecs_t pointerGestureTapInterval; + + // Tap drag gesture delay time. + // The time between the previous tap's up and the next down must be less than + // this to be considered a drag. Otherwise, the previous tap is finished and a + // new tap begins. + // + // Note that the previous tap will be held down for this entire duration so this + // interval must be shorter than the long press timeout. + nsecs_t pointerGestureTapDragInterval; + + // The distance in pixels that the pointer is allowed to move from initial down + // to up and still be called a tap. + float pointerGestureTapSlop; // in pixels + + // Time after the first touch points go down to settle on an initial centroid. + // This is intended to be enough time to handle cases where the user puts down two + // fingers at almost but not quite exactly the same time. + nsecs_t pointerGestureMultitouchSettleInterval; + + // The transition from PRESS to SWIPE or FREEFORM gesture mode is made when + // at least two pointers have moved at least this far from their starting place. + float pointerGestureMultitouchMinDistance; // in pixels + + // The transition from PRESS to SWIPE gesture mode can only occur when the + // cosine of the angle between the two vectors is greater than or equal to than this value + // which indicates that the vectors are oriented in the same direction. + // When the vectors are oriented in the exactly same direction, the cosine is 1.0. + // (In exactly opposite directions, the cosine is -1.0.) + float pointerGestureSwipeTransitionAngleCosine; + + // The transition from PRESS to SWIPE gesture mode can only occur when the + // fingers are no more than this far apart relative to the diagonal size of + // the touch pad. For example, a ratio of 0.5 means that the fingers must be + // no more than half the diagonal size of the touch pad apart. + float pointerGestureSwipeMaxWidthRatio; + + // The gesture movement speed factor relative to the size of the display. + // Movement speed applies when the fingers are moving in the same direction. + // Without acceleration, a full swipe of the touch pad diagonal in movement mode + // will cover this portion of the display diagonal. + float pointerGestureMovementSpeedRatio; + + // The gesture zoom speed factor relative to the size of the display. + // Zoom speed applies when the fingers are mostly moving relative to each other + // to execute a scale gesture or similar. + // Without acceleration, a full swipe of the touch pad diagonal in zoom mode + // will cover this portion of the display diagonal. + float pointerGestureZoomSpeedRatio; + + // True to show the location of touches on the touch screen as spots. + bool showTouches; + + InputReaderConfiguration() : + virtualKeyQuietTime(0), + pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f), + wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f), + pointerGesturesEnabled(true), + pointerGestureQuietInterval(100 * 1000000LL), // 100 ms + pointerGestureDragMinSwitchSpeed(50), // 50 pixels per second + pointerGestureTapInterval(150 * 1000000LL), // 150 ms + pointerGestureTapDragInterval(150 * 1000000LL), // 150 ms + pointerGestureTapSlop(10.0f), // 10 pixels + pointerGestureMultitouchSettleInterval(100 * 1000000LL), // 100 ms + pointerGestureMultitouchMinDistance(15), // 15 pixels + pointerGestureSwipeTransitionAngleCosine(0.2588f), // cosine of 75 degrees + pointerGestureSwipeMaxWidthRatio(0.25f), + pointerGestureMovementSpeedRatio(0.8f), + pointerGestureZoomSpeedRatio(0.3f), + showTouches(false) { } + + bool getDisplayInfo(bool external, DisplayViewport* outViewport) const; + void setDisplayInfo(bool external, const DisplayViewport& viewport); + +private: + DisplayViewport mInternalDisplay; + DisplayViewport mExternalDisplay; +}; + + +/* + * 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. + * + * These methods must NOT re-enter the input reader since they may be called while + * holding the input reader lock. + */ +class InputReaderPolicyInterface : public virtual RefBase { +protected: + InputReaderPolicyInterface() { } + virtual ~InputReaderPolicyInterface() { } + +public: + /* Gets the input reader configuration. */ + virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0; + + /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */ + virtual sp obtainPointerController(int32_t deviceId) = 0; + + /* Notifies the input reader policy that some input devices have changed + * and provides information about all current input devices. + */ + virtual void notifyInputDevicesChanged(const Vector& inputDevices) = 0; + + /* Gets the keyboard layout for a particular input device. */ + virtual sp getKeyboardLayoutOverlay( + const InputDeviceIdentifier& identifier) = 0; + + /* Gets a user-supplied alias for a particular input device, or an empty string if none. */ + virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) = 0; +}; + + +/* Processes raw input events and sends cooked event data to an input listener. */ +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; + + /* Called by the heatbeat to ensures that the reader has not deadlocked. */ + virtual void monitor() = 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 information about all input devices. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void getInputDevices(Vector& outInputDevices) = 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; + + /* Requests that a reconfiguration of all input devices. + * The changes flag is a bitfield that indicates what has changed and whether + * the input devices must all be reopened. */ + virtual void requestRefreshConfiguration(uint32_t changes) = 0; + + /* Controls the vibrator of a particular input device. */ + virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize, + ssize_t repeat, int32_t token) = 0; + virtual void cancelVibrate(int32_t deviceId, int32_t token) = 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 void disableVirtualKeysUntil(nsecs_t time) = 0; + virtual bool shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode) = 0; + + virtual void fadePointer() = 0; + + virtual void requestTimeoutAtTime(nsecs_t when) = 0; + virtual int32_t bumpGeneration() = 0; + + virtual InputReaderPolicyInterface* getPolicy() = 0; + virtual InputListenerInterface* getListener() = 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 listener. Some functions of the input reader, such as early + * event filtering in low power states, are controlled by a separate policy object. + * + * The InputReader owns a collection of InputMappers. Most of the work it does happens + * on the input reader thread but the InputReader can receive queries from other system + * components running on arbitrary threads. To keep things manageable, the InputReader + * uses a single Mutex to guard its state. The Mutex may be held while calling into the + * EventHub or the InputReaderPolicy but it is never held while calling into the + * InputListener. + */ +class InputReader : public InputReaderInterface { +public: + InputReader(const sp& eventHub, + const sp& policy, + const sp& listener); + virtual ~InputReader(); + + virtual void dump(String8& dump); + virtual void monitor(); + + virtual void loopOnce(); + + virtual void getInputDevices(Vector& outInputDevices); + + 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); + + virtual void requestRefreshConfiguration(uint32_t changes); + + virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize, + ssize_t repeat, int32_t token); + virtual void cancelVibrate(int32_t deviceId, int32_t token); + +protected: + // These members are protected so they can be instrumented by test cases. + virtual InputDevice* createDeviceLocked(int32_t deviceId, int32_t controllerNumber, + const InputDeviceIdentifier& identifier, uint32_t classes); + + class ContextImpl : public InputReaderContext { + InputReader* mReader; + + public: + ContextImpl(InputReader* reader); + + virtual void updateGlobalMetaState(); + virtual int32_t getGlobalMetaState(); + virtual void disableVirtualKeysUntil(nsecs_t time); + virtual bool shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode); + virtual void fadePointer(); + virtual void requestTimeoutAtTime(nsecs_t when); + virtual int32_t bumpGeneration(); + virtual InputReaderPolicyInterface* getPolicy(); + virtual InputListenerInterface* getListener(); + virtual EventHubInterface* getEventHub(); + } mContext; + + friend class ContextImpl; + +private: + Mutex mLock; + + Condition mReaderIsAliveCondition; + + sp mEventHub; + sp mPolicy; + sp mQueuedListener; + + InputReaderConfiguration mConfig; + + // The event queue. + static const int EVENT_BUFFER_SIZE = 256; + RawEvent mEventBuffer[EVENT_BUFFER_SIZE]; + + KeyedVector mDevices; + + // low-level input event decoding and device management + void processEventsLocked(const RawEvent* rawEvents, size_t count); + + void addDeviceLocked(nsecs_t when, int32_t deviceId); + void removeDeviceLocked(nsecs_t when, int32_t deviceId); + void processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count); + void timeoutExpiredLocked(nsecs_t when); + + void handleConfigurationChangedLocked(nsecs_t when); + + int32_t mGlobalMetaState; + void updateGlobalMetaStateLocked(); + int32_t getGlobalMetaStateLocked(); + + void fadePointerLocked(); + + int32_t mGeneration; + int32_t bumpGenerationLocked(); + + void getInputDevicesLocked(Vector& outInputDevices); + + nsecs_t mDisableVirtualKeysTimeout; + void disableVirtualKeysUntilLocked(nsecs_t time); + bool shouldDropVirtualKeyLocked(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode); + + nsecs_t mNextTimeout; + void requestTimeoutAtTimeLocked(nsecs_t when); + + uint32_t mConfigurationChangesToRefresh; + void refreshConfigurationLocked(uint32_t changes); + + // state queries + typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code); + int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code, + GetStateFunc getStateFunc); + bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); +}; + + +/* Reads raw events from the event hub and processes them, endlessly. */ +class InputReaderThread : public Thread { +public: + InputReaderThread(const sp& reader); + virtual ~InputReaderThread(); + +private: + sp mReader; + + virtual bool threadLoop(); +}; + + +/* Represents the state of a single input device. */ +class InputDevice { +public: + InputDevice(InputReaderContext* context, int32_t id, int32_t generation, int32_t + controllerNumber, const InputDeviceIdentifier& identifier, uint32_t classes); + ~InputDevice(); + + inline InputReaderContext* getContext() { return mContext; } + inline int32_t getId() const { return mId; } + inline int32_t getControllerNumber() const { return mControllerNumber; } + inline int32_t getGeneration() const { return mGeneration; } + inline const String8& getName() const { return mIdentifier.name; } + inline uint32_t getClasses() const { return mClasses; } + inline uint32_t getSources() const { return mSources; } + + inline bool isExternal() { return mIsExternal; } + inline void setExternal(bool external) { mIsExternal = external; } + + inline bool isIgnored() { return mMappers.isEmpty(); } + + void dump(String8& dump); + void addMapper(InputMapper* mapper); + void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); + void reset(nsecs_t when); + void process(const RawEvent* rawEvents, size_t count); + void timeoutExpired(nsecs_t when); + + void getDeviceInfo(InputDeviceInfo* outDeviceInfo); + int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token); + void cancelVibrate(int32_t token); + + int32_t getMetaState(); + + void fadePointer(); + + void bumpGeneration(); + + void notifyReset(nsecs_t when); + + inline const PropertyMap& getConfiguration() { return mConfiguration; } + inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } + + bool hasKey(int32_t code) { + return getEventHub()->hasScanCode(mId, code); + } + + bool hasAbsoluteAxis(int32_t code) { + RawAbsoluteAxisInfo info; + getEventHub()->getAbsoluteAxisInfo(mId, code, &info); + return info.valid; + } + + bool isKeyPressed(int32_t code) { + return getEventHub()->getScanCodeState(mId, code) == AKEY_STATE_DOWN; + } + + int32_t getAbsoluteAxisValue(int32_t code) { + int32_t value; + getEventHub()->getAbsoluteAxisValue(mId, code, &value); + return value; + } + +private: + InputReaderContext* mContext; + int32_t mId; + int32_t mGeneration; + int32_t mControllerNumber; + InputDeviceIdentifier mIdentifier; + String8 mAlias; + uint32_t mClasses; + + Vector mMappers; + + uint32_t mSources; + bool mIsExternal; + bool mDropUntilNextSync; + + typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code); + int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); + + PropertyMap mConfiguration; +}; + + +/* Keeps track of the state of mouse or touch pad buttons. */ +class CursorButtonAccumulator { +public: + CursorButtonAccumulator(); + void reset(InputDevice* device); + + void process(const RawEvent* rawEvent); + + uint32_t getButtonState() const; + +private: + bool mBtnLeft; + bool mBtnRight; + bool mBtnMiddle; + bool mBtnBack; + bool mBtnSide; + bool mBtnForward; + bool mBtnExtra; + bool mBtnTask; + + void clearButtons(); +}; + + +/* Keeps track of cursor movements. */ + +class CursorMotionAccumulator { +public: + CursorMotionAccumulator(); + void reset(InputDevice* device); + + void process(const RawEvent* rawEvent); + void finishSync(); + + inline int32_t getRelativeX() const { return mRelX; } + inline int32_t getRelativeY() const { return mRelY; } + +private: + int32_t mRelX; + int32_t mRelY; + + void clearRelativeAxes(); +}; + + +/* Keeps track of cursor scrolling motions. */ + +class CursorScrollAccumulator { +public: + CursorScrollAccumulator(); + void configure(InputDevice* device); + void reset(InputDevice* device); + + void process(const RawEvent* rawEvent); + void finishSync(); + + inline bool haveRelativeVWheel() const { return mHaveRelWheel; } + inline bool haveRelativeHWheel() const { return mHaveRelHWheel; } + + inline int32_t getRelativeX() const { return mRelX; } + inline int32_t getRelativeY() const { return mRelY; } + inline int32_t getRelativeVWheel() const { return mRelWheel; } + inline int32_t getRelativeHWheel() const { return mRelHWheel; } + +private: + bool mHaveRelWheel; + bool mHaveRelHWheel; + + int32_t mRelX; + int32_t mRelY; + int32_t mRelWheel; + int32_t mRelHWheel; + + void clearRelativeAxes(); +}; + + +/* Keeps track of the state of touch, stylus and tool buttons. */ +class TouchButtonAccumulator { +public: + TouchButtonAccumulator(); + void configure(InputDevice* device); + void reset(InputDevice* device); + + void process(const RawEvent* rawEvent); + + uint32_t getButtonState() const; + int32_t getToolType() const; + bool isToolActive() const; + bool isHovering() const; + bool hasStylus() const; + +private: + bool mHaveBtnTouch; + bool mHaveStylus; + + bool mBtnTouch; + bool mBtnStylus; + bool mBtnStylus2; + bool mBtnToolFinger; + bool mBtnToolPen; + bool mBtnToolRubber; + bool mBtnToolBrush; + bool mBtnToolPencil; + bool mBtnToolAirbrush; + bool mBtnToolMouse; + bool mBtnToolLens; + bool mBtnToolDoubleTap; + bool mBtnToolTripleTap; + bool mBtnToolQuadTap; + + void clearButtons(); +}; + + +/* Raw axis information from the driver. */ +struct RawPointerAxes { + RawAbsoluteAxisInfo x; + RawAbsoluteAxisInfo y; + RawAbsoluteAxisInfo pressure; + RawAbsoluteAxisInfo touchMajor; + RawAbsoluteAxisInfo touchMinor; + RawAbsoluteAxisInfo toolMajor; + RawAbsoluteAxisInfo toolMinor; + RawAbsoluteAxisInfo orientation; + RawAbsoluteAxisInfo distance; + RawAbsoluteAxisInfo tiltX; + RawAbsoluteAxisInfo tiltY; + RawAbsoluteAxisInfo trackingId; + RawAbsoluteAxisInfo slot; + + RawPointerAxes(); + void clear(); +}; + + +/* Raw data for a collection of pointers including a pointer id mapping table. */ +struct RawPointerData { + struct Pointer { + uint32_t id; + int32_t x; + int32_t y; + int32_t pressure; + int32_t touchMajor; + int32_t touchMinor; + int32_t toolMajor; + int32_t toolMinor; + int32_t orientation; + int32_t distance; + int32_t tiltX; + int32_t tiltY; + int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant + bool isHovering; + }; + + uint32_t pointerCount; + Pointer pointers[MAX_POINTERS]; + BitSet32 hoveringIdBits, touchingIdBits; + uint32_t idToIndex[MAX_POINTER_ID + 1]; + + RawPointerData(); + void clear(); + void copyFrom(const RawPointerData& other); + void getCentroidOfTouchingPointers(float* outX, float* outY) const; + + inline void markIdBit(uint32_t id, bool isHovering) { + if (isHovering) { + hoveringIdBits.markBit(id); + } else { + touchingIdBits.markBit(id); + } + } + + inline void clearIdBits() { + hoveringIdBits.clear(); + touchingIdBits.clear(); + } + + inline const Pointer& pointerForId(uint32_t id) const { + return pointers[idToIndex[id]]; + } + + inline bool isHovering(uint32_t pointerIndex) { + return pointers[pointerIndex].isHovering; + } +}; + + +/* Cooked data for a collection of pointers including a pointer id mapping table. */ +struct CookedPointerData { + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + BitSet32 hoveringIdBits, touchingIdBits; + uint32_t idToIndex[MAX_POINTER_ID + 1]; + + CookedPointerData(); + void clear(); + void copyFrom(const CookedPointerData& other); + + inline const PointerCoords& pointerCoordsForId(uint32_t id) const { + return pointerCoords[idToIndex[id]]; + } + + inline bool isHovering(uint32_t pointerIndex) { + return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id); + } +}; + + +/* Keeps track of the state of single-touch protocol. */ +class SingleTouchMotionAccumulator { +public: + SingleTouchMotionAccumulator(); + + void process(const RawEvent* rawEvent); + void reset(InputDevice* device); + + inline int32_t getAbsoluteX() const { return mAbsX; } + inline int32_t getAbsoluteY() const { return mAbsY; } + inline int32_t getAbsolutePressure() const { return mAbsPressure; } + inline int32_t getAbsoluteToolWidth() const { return mAbsToolWidth; } + inline int32_t getAbsoluteDistance() const { return mAbsDistance; } + inline int32_t getAbsoluteTiltX() const { return mAbsTiltX; } + inline int32_t getAbsoluteTiltY() const { return mAbsTiltY; } + +private: + int32_t mAbsX; + int32_t mAbsY; + int32_t mAbsPressure; + int32_t mAbsToolWidth; + int32_t mAbsDistance; + int32_t mAbsTiltX; + int32_t mAbsTiltY; + + void clearAbsoluteAxes(); +}; + + +/* Keeps track of the state of multi-touch protocol. */ +class MultiTouchMotionAccumulator { +public: + class Slot { + public: + inline bool isInUse() const { return mInUse; } + inline int32_t getX() const { return mAbsMTPositionX; } + inline int32_t getY() const { return mAbsMTPositionY; } + inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; } + inline int32_t getTouchMinor() const { + return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; } + inline int32_t getToolMajor() const { return mAbsMTWidthMajor; } + inline int32_t getToolMinor() const { + return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; } + inline int32_t getOrientation() const { return mAbsMTOrientation; } + inline int32_t getTrackingId() const { return mAbsMTTrackingId; } + inline int32_t getPressure() const { return mAbsMTPressure; } + inline int32_t getDistance() const { return mAbsMTDistance; } + inline int32_t getToolType() const; + + private: + friend class MultiTouchMotionAccumulator; + + bool mInUse; + bool mHaveAbsMTTouchMinor; + bool mHaveAbsMTWidthMinor; + bool mHaveAbsMTToolType; + + int32_t mAbsMTPositionX; + int32_t mAbsMTPositionY; + int32_t mAbsMTTouchMajor; + int32_t mAbsMTTouchMinor; + int32_t mAbsMTWidthMajor; + int32_t mAbsMTWidthMinor; + int32_t mAbsMTOrientation; + int32_t mAbsMTTrackingId; + int32_t mAbsMTPressure; + int32_t mAbsMTDistance; + int32_t mAbsMTToolType; + + Slot(); + void clear(); + }; + + MultiTouchMotionAccumulator(); + ~MultiTouchMotionAccumulator(); + + void configure(InputDevice* device, size_t slotCount, bool usingSlotsProtocol); + void reset(InputDevice* device); + void process(const RawEvent* rawEvent); + void finishSync(); + bool hasStylus() const; + + inline size_t getSlotCount() const { return mSlotCount; } + inline const Slot* getSlot(size_t index) const { return &mSlots[index]; } + +private: + int32_t mCurrentSlot; + Slot* mSlots; + size_t mSlotCount; + bool mUsingSlotsProtocol; + bool mHaveStylus; + + void clearSlots(int32_t initialSlot); +}; + + +/* An input mapper transforms raw input events into cooked event data. + * A single input device can have multiple associated input mappers in order to interpret + * different classes of events. + * + * InputMapper lifecycle: + * - create + * - configure with 0 changes + * - reset + * - process, process, process (may occasionally reconfigure with non-zero changes or reset) + * - reset + * - destroy + */ +class InputMapper { +public: + 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 InputListenerInterface* getListener() { return mContext->getListener(); } + inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } + + virtual uint32_t getSources() = 0; + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void dump(String8& dump); + virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); + virtual void reset(nsecs_t when); + virtual void process(const RawEvent* rawEvent) = 0; + virtual void timeoutExpired(nsecs_t when); + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, + int32_t token); + virtual void cancelVibrate(int32_t token); + + virtual int32_t getMetaState(); + + virtual void fadePointer(); + +protected: + InputDevice* mDevice; + InputReaderContext* mContext; + + status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo); + void bumpGeneration(); + + static void dumpRawAbsoluteAxisInfo(String8& dump, + const RawAbsoluteAxisInfo& axis, const char* name); +}; + + +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: + uint32_t mUpdatedSwitchValues; + uint32_t mUpdatedSwitchMask; + + void processSwitch(int32_t switchCode, int32_t switchValue); + void sync(nsecs_t when); +}; + + +class VibratorInputMapper : public InputMapper { +public: + VibratorInputMapper(InputDevice* device); + virtual ~VibratorInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void process(const RawEvent* rawEvent); + + virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, + int32_t token); + virtual void cancelVibrate(int32_t token); + virtual void timeoutExpired(nsecs_t when); + virtual void dump(String8& dump); + +private: + bool mVibrating; + nsecs_t mPattern[MAX_VIBRATE_PATTERN_SIZE]; + size_t mPatternSize; + ssize_t mRepeat; + int32_t mToken; + ssize_t mIndex; + nsecs_t mNextStepTime; + + void nextStep(); + void stopVibrating(); +}; + + +class KeyboardInputMapper : public InputMapper { +public: + KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType); + virtual ~KeyboardInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void dump(String8& dump); + virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); + virtual void reset(nsecs_t when); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + virtual int32_t getMetaState(); + +private: + struct KeyDown { + int32_t keyCode; + int32_t scanCode; + }; + + uint32_t mSource; + int32_t mKeyboardType; + + int32_t mOrientation; // orientation for dpad keys + + Vector mKeyDowns; // keys that are down + int32_t mMetaState; + nsecs_t mDownTime; // time of most recent key down + + int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none + + struct LedState { + bool avail; // led is available + bool on; // we think the led is currently on + }; + LedState mCapsLockLedState; + LedState mNumLockLedState; + LedState mScrollLockLedState; + + // Immutable configuration parameters. + struct Parameters { + bool hasAssociatedDisplay; + bool orientationAware; + } mParameters; + + 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 findKeyDown(int32_t scanCode); + + void resetLedState(); + void initializeLedState(LedState& ledState, int32_t led); + void updateLedState(bool reset); + void updateLedStateForModifier(LedState& ledState, int32_t led, + int32_t modifier, bool reset); +}; + + +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(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); + virtual void reset(nsecs_t when); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + + virtual void fadePointer(); + +private: + // Amount that trackball needs to move in order to generate a key event. + static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6; + + // Immutable configuration parameters. + struct Parameters { + enum Mode { + MODE_POINTER, + MODE_NAVIGATION, + }; + + Mode mode; + bool hasAssociatedDisplay; + bool orientationAware; + } mParameters; + + CursorButtonAccumulator mCursorButtonAccumulator; + CursorMotionAccumulator mCursorMotionAccumulator; + CursorScrollAccumulator mCursorScrollAccumulator; + + int32_t mSource; + float mXScale; + float mYScale; + float mXPrecision; + float mYPrecision; + + float mVWheelScale; + float mHWheelScale; + + // Velocity controls for mouse pointer and wheel movements. + // The controls for X and Y wheel movements are separate to keep them decoupled. + VelocityControl mPointerVelocityControl; + VelocityControl mWheelXVelocityControl; + VelocityControl mWheelYVelocityControl; + + int32_t mOrientation; + + sp mPointerController; + + int32_t mButtonState; + nsecs_t mDownTime; + + 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(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); + virtual void reset(nsecs_t when); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + virtual void fadePointer(); + virtual void timeoutExpired(nsecs_t when); + +protected: + CursorButtonAccumulator mCursorButtonAccumulator; + CursorScrollAccumulator mCursorScrollAccumulator; + TouchButtonAccumulator mTouchButtonAccumulator; + + struct VirtualKey { + int32_t keyCode; + int32_t scanCode; + uint32_t flags; + + // computed hit box, specified in touch screen coords based on known display size + int32_t hitLeft; + int32_t hitTop; + int32_t hitRight; + int32_t hitBottom; + + inline bool isHit(int32_t x, int32_t y) const { + return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom; + } + }; + + // Input sources and device mode. + uint32_t mSource; + + enum DeviceMode { + DEVICE_MODE_DISABLED, // input is disabled + DEVICE_MODE_DIRECT, // direct mapping (touchscreen) + DEVICE_MODE_UNSCALED, // unscaled mapping (touchpad) + DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation) + DEVICE_MODE_POINTER, // pointer mapping (pointer) + }; + DeviceMode mDeviceMode; + + // The reader's configuration. + InputReaderConfiguration mConfig; + + // Immutable configuration parameters. + struct Parameters { + enum DeviceType { + DEVICE_TYPE_TOUCH_SCREEN, + DEVICE_TYPE_TOUCH_PAD, + DEVICE_TYPE_TOUCH_NAVIGATION, + DEVICE_TYPE_POINTER, + }; + + DeviceType deviceType; + bool hasAssociatedDisplay; + bool associatedDisplayIsExternal; + bool orientationAware; + bool hasButtonUnderPad; + + enum GestureMode { + GESTURE_MODE_POINTER, + GESTURE_MODE_SPOTS, + }; + GestureMode gestureMode; + } mParameters; + + // Immutable calibration parameters in parsed form. + struct Calibration { + // Size + enum SizeCalibration { + SIZE_CALIBRATION_DEFAULT, + SIZE_CALIBRATION_NONE, + SIZE_CALIBRATION_GEOMETRIC, + SIZE_CALIBRATION_DIAMETER, + SIZE_CALIBRATION_BOX, + SIZE_CALIBRATION_AREA, + }; + + SizeCalibration sizeCalibration; + + bool haveSizeScale; + float sizeScale; + bool haveSizeBias; + float sizeBias; + bool haveSizeIsSummed; + bool sizeIsSummed; + + // Pressure + enum PressureCalibration { + PRESSURE_CALIBRATION_DEFAULT, + PRESSURE_CALIBRATION_NONE, + PRESSURE_CALIBRATION_PHYSICAL, + PRESSURE_CALIBRATION_AMPLITUDE, + }; + + PressureCalibration pressureCalibration; + bool havePressureScale; + float pressureScale; + + // Orientation + enum OrientationCalibration { + ORIENTATION_CALIBRATION_DEFAULT, + ORIENTATION_CALIBRATION_NONE, + ORIENTATION_CALIBRATION_INTERPOLATED, + ORIENTATION_CALIBRATION_VECTOR, + }; + + OrientationCalibration orientationCalibration; + + // Distance + enum DistanceCalibration { + DISTANCE_CALIBRATION_DEFAULT, + DISTANCE_CALIBRATION_NONE, + DISTANCE_CALIBRATION_SCALED, + }; + + DistanceCalibration distanceCalibration; + bool haveDistanceScale; + float distanceScale; + + enum CoverageCalibration { + COVERAGE_CALIBRATION_DEFAULT, + COVERAGE_CALIBRATION_NONE, + COVERAGE_CALIBRATION_BOX, + }; + + CoverageCalibration coverageCalibration; + + inline void applySizeScaleAndBias(float* outSize) const { + if (haveSizeScale) { + *outSize *= sizeScale; + } + if (haveSizeBias) { + *outSize += sizeBias; + } + if (*outSize < 0) { + *outSize = 0; + } + } + } mCalibration; + + // Raw pointer axis information from the driver. + RawPointerAxes mRawPointerAxes; + + // Raw pointer sample data. + RawPointerData mCurrentRawPointerData; + RawPointerData mLastRawPointerData; + + // Cooked pointer sample data. + CookedPointerData mCurrentCookedPointerData; + CookedPointerData mLastCookedPointerData; + + // Button state. + int32_t mCurrentButtonState; + int32_t mLastButtonState; + + // Scroll state. + int32_t mCurrentRawVScroll; + int32_t mCurrentRawHScroll; + + // Id bits used to differentiate fingers, stylus and mouse tools. + BitSet32 mCurrentFingerIdBits; // finger or unknown + BitSet32 mLastFingerIdBits; + BitSet32 mCurrentStylusIdBits; // stylus or eraser + BitSet32 mLastStylusIdBits; + BitSet32 mCurrentMouseIdBits; // mouse or lens + BitSet32 mLastMouseIdBits; + + // True if we sent a HOVER_ENTER event. + bool mSentHoverEnter; + + // The time the primary pointer last went down. + nsecs_t mDownTime; + + // The pointer controller, or null if the device is not a pointer. + sp mPointerController; + + Vector mVirtualKeys; + + virtual void configureParameters(); + virtual void dumpParameters(String8& dump); + virtual void configureRawPointerAxes(); + virtual void dumpRawPointerAxes(String8& dump); + virtual void configureSurface(nsecs_t when, bool* outResetNeeded); + virtual void dumpSurface(String8& dump); + virtual void configureVirtualKeys(); + virtual void dumpVirtualKeys(String8& dump); + virtual void parseCalibration(); + virtual void resolveCalibration(); + virtual void dumpCalibration(String8& dump); + virtual bool hasStylus() const = 0; + + virtual void syncTouch(nsecs_t when, bool* outHavePointerIds) = 0; + +private: + // The current viewport. + // The components of the viewport are specified in the display's rotated orientation. + DisplayViewport mViewport; + + // The surface orientation, width and height set by configureSurface(). + // The width and height are derived from the viewport but are specified + // in the natural orientation. + // The surface origin specifies how the surface coordinates should be translated + // to align with the logical display coordinate space. + // The orientation may be different from the viewport orientation as it specifies + // the rotation of the surface coordinates required to produce the viewport's + // requested orientation, so it will depend on whether the device is orientation aware. + int32_t mSurfaceWidth; + int32_t mSurfaceHeight; + int32_t mSurfaceLeft; + int32_t mSurfaceTop; + int32_t mSurfaceOrientation; + + // Translation and scaling factors, orientation-independent. + float mXTranslate; + float mXScale; + float mXPrecision; + + float mYTranslate; + float mYScale; + float mYPrecision; + + float mGeometricScale; + + float mPressureScale; + + float mSizeScale; + + float mOrientationScale; + + float mDistanceScale; + + bool mHaveTilt; + float mTiltXCenter; + float mTiltXScale; + float mTiltYCenter; + float mTiltYScale; + + // Oriented motion ranges for input device info. + struct OrientedRanges { + InputDeviceInfo::MotionRange x; + InputDeviceInfo::MotionRange y; + InputDeviceInfo::MotionRange pressure; + + bool haveSize; + InputDeviceInfo::MotionRange size; + + bool haveTouchSize; + InputDeviceInfo::MotionRange touchMajor; + InputDeviceInfo::MotionRange touchMinor; + + bool haveToolSize; + InputDeviceInfo::MotionRange toolMajor; + InputDeviceInfo::MotionRange toolMinor; + + bool haveOrientation; + InputDeviceInfo::MotionRange orientation; + + bool haveDistance; + InputDeviceInfo::MotionRange distance; + + bool haveTilt; + InputDeviceInfo::MotionRange tilt; + + OrientedRanges() { + clear(); + } + + void clear() { + haveSize = false; + haveTouchSize = false; + haveToolSize = false; + haveOrientation = false; + haveDistance = false; + haveTilt = false; + } + } mOrientedRanges; + + // Oriented dimensions and precision. + float mOrientedXPrecision; + float mOrientedYPrecision; + + struct CurrentVirtualKeyState { + bool down; + bool ignored; + nsecs_t downTime; + int32_t keyCode; + int32_t scanCode; + } mCurrentVirtualKey; + + // Scale factor for gesture or mouse based pointer movements. + float mPointerXMovementScale; + float mPointerYMovementScale; + + // Scale factor for gesture based zooming and other freeform motions. + float mPointerXZoomScale; + float mPointerYZoomScale; + + // The maximum swipe width. + float mPointerGestureMaxSwipeWidth; + + struct PointerDistanceHeapElement { + uint32_t currentPointerIndex : 8; + uint32_t lastPointerIndex : 8; + uint64_t distance : 48; // squared distance + }; + + enum PointerUsage { + POINTER_USAGE_NONE, + POINTER_USAGE_GESTURES, + POINTER_USAGE_STYLUS, + POINTER_USAGE_MOUSE, + }; + PointerUsage mPointerUsage; + + struct PointerGesture { + enum Mode { + // No fingers, button is not pressed. + // Nothing happening. + NEUTRAL, + + // No fingers, button is not pressed. + // Tap detected. + // Emits DOWN and UP events at the pointer location. + TAP, + + // Exactly one finger dragging following a tap. + // Pointer follows the active finger. + // Emits DOWN, MOVE and UP events at the pointer location. + // + // Detect double-taps when the finger goes up while in TAP_DRAG mode. + TAP_DRAG, + + // Button is pressed. + // Pointer follows the active finger if there is one. Other fingers are ignored. + // Emits DOWN, MOVE and UP events at the pointer location. + BUTTON_CLICK_OR_DRAG, + + // Exactly one finger, button is not pressed. + // Pointer follows the active finger. + // Emits HOVER_MOVE events at the pointer location. + // + // Detect taps when the finger goes up while in HOVER mode. + HOVER, + + // Exactly two fingers but neither have moved enough to clearly indicate + // whether a swipe or freeform gesture was intended. We consider the + // pointer to be pressed so this enables clicking or long-pressing on buttons. + // Pointer does not move. + // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate. + PRESS, + + // Exactly two fingers moving in the same direction, button is not pressed. + // Pointer does not move. + // Emits DOWN, MOVE and UP events with a single pointer coordinate that + // follows the midpoint between both fingers. + SWIPE, + + // Two or more fingers moving in arbitrary directions, button is not pressed. + // Pointer does not move. + // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow + // each finger individually relative to the initial centroid of the finger. + FREEFORM, + + // Waiting for quiet time to end before starting the next gesture. + QUIET, + }; + + // Time the first finger went down. + nsecs_t firstTouchTime; + + // The active pointer id from the raw touch data. + int32_t activeTouchId; // -1 if none + + // The active pointer id from the gesture last delivered to the application. + int32_t activeGestureId; // -1 if none + + // Pointer coords and ids for the current and previous pointer gesture. + Mode currentGestureMode; + BitSet32 currentGestureIdBits; + uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1]; + PointerProperties currentGestureProperties[MAX_POINTERS]; + PointerCoords currentGestureCoords[MAX_POINTERS]; + + Mode lastGestureMode; + BitSet32 lastGestureIdBits; + uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1]; + PointerProperties lastGestureProperties[MAX_POINTERS]; + PointerCoords lastGestureCoords[MAX_POINTERS]; + + // Time the pointer gesture last went down. + nsecs_t downTime; + + // Time when the pointer went down for a TAP. + nsecs_t tapDownTime; + + // Time when the pointer went up for a TAP. + nsecs_t tapUpTime; + + // Location of initial tap. + float tapX, tapY; + + // Time we started waiting for quiescence. + nsecs_t quietTime; + + // Reference points for multitouch gestures. + float referenceTouchX; // reference touch X/Y coordinates in surface units + float referenceTouchY; + float referenceGestureX; // reference gesture X/Y coordinates in pixels + float referenceGestureY; + + // Distance that each pointer has traveled which has not yet been + // subsumed into the reference gesture position. + BitSet32 referenceIdBits; + struct Delta { + float dx, dy; + }; + Delta referenceDeltas[MAX_POINTER_ID + 1]; + + // Describes how touch ids are mapped to gesture ids for freeform gestures. + uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1]; + + // A velocity tracker for determining whether to switch active pointers during drags. + VelocityTracker velocityTracker; + + void reset() { + firstTouchTime = LLONG_MIN; + activeTouchId = -1; + activeGestureId = -1; + currentGestureMode = NEUTRAL; + currentGestureIdBits.clear(); + lastGestureMode = NEUTRAL; + lastGestureIdBits.clear(); + downTime = 0; + velocityTracker.clear(); + resetTap(); + resetQuietTime(); + } + + void resetTap() { + tapDownTime = LLONG_MIN; + tapUpTime = LLONG_MIN; + } + + void resetQuietTime() { + quietTime = LLONG_MIN; + } + } mPointerGesture; + + struct PointerSimple { + PointerCoords currentCoords; + PointerProperties currentProperties; + PointerCoords lastCoords; + PointerProperties lastProperties; + + // True if the pointer is down. + bool down; + + // True if the pointer is hovering. + bool hovering; + + // Time the pointer last went down. + nsecs_t downTime; + + void reset() { + currentCoords.clear(); + currentProperties.clear(); + lastCoords.clear(); + lastProperties.clear(); + down = false; + hovering = false; + downTime = 0; + } + } mPointerSimple; + + // The pointer and scroll velocity controls. + VelocityControl mPointerVelocityControl; + VelocityControl mWheelXVelocityControl; + VelocityControl mWheelYVelocityControl; + + void sync(nsecs_t when); + + bool consumeRawTouches(nsecs_t when, uint32_t policyFlags); + void dispatchVirtualKey(nsecs_t when, uint32_t policyFlags, + int32_t keyEventAction, int32_t keyEventFlags); + + void dispatchTouches(nsecs_t when, uint32_t policyFlags); + void dispatchHoverExit(nsecs_t when, uint32_t policyFlags); + void dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags); + void cookPointerData(); + + void dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, PointerUsage pointerUsage); + void abortPointerUsage(nsecs_t when, uint32_t policyFlags); + + void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout); + void abortPointerGestures(nsecs_t when, uint32_t policyFlags); + bool preparePointerGestures(nsecs_t when, + bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, + bool isTimeout); + + void dispatchPointerStylus(nsecs_t when, uint32_t policyFlags); + void abortPointerStylus(nsecs_t when, uint32_t policyFlags); + + void dispatchPointerMouse(nsecs_t when, uint32_t policyFlags); + void abortPointerMouse(nsecs_t when, uint32_t policyFlags); + + void dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, + bool down, bool hovering); + void abortPointerSimple(nsecs_t when, uint32_t policyFlags); + + // Dispatches a motion event. + // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the + // method will take care of setting the index and transmuting the action to DOWN or UP + // it is the first / last pointer to go down / up. + void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, + int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, + int32_t edgeFlags, + const PointerProperties* properties, const PointerCoords* coords, + const uint32_t* idToIndex, BitSet32 idBits, + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime); + + // Updates pointer coords and properties for pointers with specified ids that have moved. + // Returns true if any of them changed. + bool updateMovedPointers(const PointerProperties* inProperties, + const PointerCoords* inCoords, const uint32_t* inIdToIndex, + PointerProperties* outProperties, PointerCoords* outCoords, + const uint32_t* outIdToIndex, BitSet32 idBits) const; + + bool isPointInsideSurface(int32_t x, int32_t y); + const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y); + + void assignPointerIds(); +}; + + +class SingleTouchInputMapper : public TouchInputMapper { +public: + SingleTouchInputMapper(InputDevice* device); + virtual ~SingleTouchInputMapper(); + + virtual void reset(nsecs_t when); + virtual void process(const RawEvent* rawEvent); + +protected: + virtual void syncTouch(nsecs_t when, bool* outHavePointerIds); + virtual void configureRawPointerAxes(); + virtual bool hasStylus() const; + +private: + SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; +}; + + +class MultiTouchInputMapper : public TouchInputMapper { +public: + MultiTouchInputMapper(InputDevice* device); + virtual ~MultiTouchInputMapper(); + + virtual void reset(nsecs_t when); + virtual void process(const RawEvent* rawEvent); + +protected: + virtual void syncTouch(nsecs_t when, bool* outHavePointerIds); + virtual void configureRawPointerAxes(); + virtual bool hasStylus() const; + +private: + MultiTouchMotionAccumulator mMultiTouchMotionAccumulator; + + // Specifies the pointer id bits that are in use, and their associated tracking id. + BitSet32 mPointerIdBits; + int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1]; +}; + + +class JoystickInputMapper : public InputMapper { +public: + JoystickInputMapper(InputDevice* device); + virtual ~JoystickInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void dump(String8& dump); + virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); + virtual void reset(nsecs_t when); + virtual void process(const RawEvent* rawEvent); + +private: + struct Axis { + RawAbsoluteAxisInfo rawAxisInfo; + AxisInfo axisInfo; + + bool explicitlyMapped; // true if the axis was explicitly assigned an axis id + + float scale; // scale factor from raw to normalized values + float offset; // offset to add after scaling for normalization + float highScale; // scale factor from raw to normalized values of high split + float highOffset; // offset to add after scaling for normalization of high split + + float min; // normalized inclusive minimum + float max; // normalized inclusive maximum + float flat; // normalized flat region size + float fuzz; // normalized error tolerance + float resolution; // normalized resolution in units/mm + + float filter; // filter out small variations of this size + float currentValue; // current value + float newValue; // most recent value + float highCurrentValue; // current value of high split + float highNewValue; // most recent value of high split + + void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo, + bool explicitlyMapped, float scale, float offset, + float highScale, float highOffset, + float min, float max, float flat, float fuzz, float resolution) { + this->rawAxisInfo = rawAxisInfo; + this->axisInfo = axisInfo; + this->explicitlyMapped = explicitlyMapped; + this->scale = scale; + this->offset = offset; + this->highScale = highScale; + this->highOffset = highOffset; + this->min = min; + this->max = max; + this->flat = flat; + this->fuzz = fuzz; + this->resolution = resolution; + this->filter = 0; + resetValue(); + } + + void resetValue() { + this->currentValue = 0; + this->newValue = 0; + this->highCurrentValue = 0; + this->highNewValue = 0; + } + }; + + // Axes indexed by raw ABS_* axis index. + KeyedVector mAxes; + + void sync(nsecs_t when, bool force); + + bool haveAxis(int32_t axisId); + void pruneAxes(bool ignoreExplicitlyMappedAxes); + bool filterAxes(bool force); + + static bool hasValueChangedSignificantly(float filter, + float newValue, float currentValue, float min, float max); + static bool hasMovedNearerToValueWithinFilteredRange(float filter, + float newValue, float currentValue, float thresholdValue); + + static bool isCenteredAxis(int32_t axis); + static int32_t getCompatAxis(int32_t axis); + + static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info); + static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis, + float value); +}; + +} // namespace android + +#endif // _UI_INPUT_READER_H diff --git a/services/inputflinger/InputWindow.cpp b/services/inputflinger/InputWindow.cpp new file mode 100644 index 000000000..da5915919 --- /dev/null +++ b/services/inputflinger/InputWindow.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2011 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 "InputWindow" +#define LOG_NDEBUG 0 + +#include "InputWindow.h" + +#include + +#include +#include + +namespace android { + +// --- InputWindowInfo --- +void InputWindowInfo::addTouchableRegion(const Rect& region) { + touchableRegion.orSelf(region); +} + +bool InputWindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const { + return touchableRegion.contains(x,y); +} + +bool InputWindowInfo::frameContainsPoint(int32_t x, int32_t y) const { + return x >= frameLeft && x <= frameRight + && y >= frameTop && y <= frameBottom; +} + +bool InputWindowInfo::isTrustedOverlay() const { + return layoutParamsType == TYPE_INPUT_METHOD + || layoutParamsType == TYPE_INPUT_METHOD_DIALOG + || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY; +} + +bool InputWindowInfo::supportsSplitTouch() const { + return layoutParamsFlags & FLAG_SPLIT_TOUCH; +} + + +// --- InputWindowHandle --- + +InputWindowHandle::InputWindowHandle(const sp& inputApplicationHandle) : + inputApplicationHandle(inputApplicationHandle), mInfo(NULL) { +} + +InputWindowHandle::~InputWindowHandle() { + delete mInfo; +} + +void InputWindowHandle::releaseInfo() { + if (mInfo) { + delete mInfo; + mInfo = NULL; + } +} + +} // namespace android diff --git a/services/inputflinger/InputWindow.h b/services/inputflinger/InputWindow.h new file mode 100644 index 000000000..9618ffe00 --- /dev/null +++ b/services/inputflinger/InputWindow.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2011 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_WINDOW_H +#define _UI_INPUT_WINDOW_H + +#include +#include +#include +#include +#include +#include +#include + +#include "InputApplication.h" + +namespace android { + + +/* + * Describes the properties of a window that can receive input. + */ +struct InputWindowInfo { + // 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_SLIPPERY = 0x20000000, + FLAG_NEEDS_MENU_KEY = 0x40000000, + }; + + // Private Window flags from WindowManager.LayoutParams + enum { + PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100, + }; + + // 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_PANEL = FIRST_SYSTEM_WINDOW+14, + TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15, + TYPE_DRAG = FIRST_SYSTEM_WINDOW+16, + TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17, + TYPE_POINTER = FIRST_SYSTEM_WINDOW+18, + TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19, + TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20, + TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21, + LAST_SYSTEM_WINDOW = 2999, + }; + + enum { + INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES = 0x00000001, + INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002, + INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004, + }; + + sp inputChannel; + String8 name; + int32_t layoutParamsFlags; + int32_t layoutParamsPrivateFlags; + int32_t layoutParamsType; + nsecs_t dispatchingTimeout; + int32_t frameLeft; + int32_t frameTop; + int32_t frameRight; + int32_t frameBottom; + float scaleFactor; + Region touchableRegion; + bool visible; + bool canReceiveKeys; + bool hasFocus; + bool hasWallpaper; + bool paused; + int32_t layer; + int32_t ownerPid; + int32_t ownerUid; + int32_t inputFeatures; + int32_t displayId; + + void addTouchableRegion(const Rect& region); + + bool touchableRegionContainsPoint(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; +}; + + +/* + * Handle for a window that can receive input. + * + * Used by the native input dispatcher to indirectly refer to the window manager objects + * that describe a window. + */ +class InputWindowHandle : public RefBase { +public: + const sp inputApplicationHandle; + + inline const InputWindowInfo* getInfo() const { + return mInfo; + } + + inline sp getInputChannel() const { + return mInfo ? mInfo->inputChannel : NULL; + } + + inline String8 getName() const { + return mInfo ? mInfo->name : String8(""); + } + + inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const { + return mInfo ? mInfo->dispatchingTimeout : defaultValue; + } + + /** + * Requests that the state of this object be updated to reflect + * the most current available information about the application. + * + * This method should only be called from within the input dispatcher's + * critical section. + * + * Returns true on success, or false if the handle is no longer valid. + */ + virtual bool updateInfo() = 0; + + /** + * Releases the storage used by the associated information when it is + * no longer needed. + */ + void releaseInfo(); + +protected: + InputWindowHandle(const sp& inputApplicationHandle); + virtual ~InputWindowHandle(); + + InputWindowInfo* mInfo; +}; + +} // namespace android + +#endif // _UI_INPUT_WINDOW_H diff --git a/services/inputflinger/PointerControllerInterface.h b/services/inputflinger/PointerControllerInterface.h new file mode 100644 index 000000000..e94dd9486 --- /dev/null +++ b/services/inputflinger/PointerControllerInterface.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H +#define _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H + +#include +#include +#include + +namespace android { + +/** + * Interface for tracking a mouse / touch pad pointer and touch pad spots. + * + * The spots are sprites on screen that visually represent the positions of + * fingers + * + * 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(int32_t buttonState) = 0; + + /* Gets a mask that indicates which buttons are pressed. */ + virtual int32_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; + + enum Transition { + // Fade/unfade immediately. + TRANSITION_IMMEDIATE, + // Fade/unfade gradually. + TRANSITION_GRADUAL, + }; + + /* Fades the pointer out now. */ + virtual void fade(Transition transition) = 0; + + /* Makes the pointer visible if it has faded out. + * The pointer never unfades itself automatically. This method must be called + * by the client whenever the pointer is moved or a button is pressed and it + * wants to ensure that the pointer becomes visible again. */ + virtual void unfade(Transition transition) = 0; + + enum Presentation { + // Show the mouse pointer. + PRESENTATION_POINTER, + // Show spots and a spot anchor in place of the mouse pointer. + PRESENTATION_SPOT, + }; + + /* Sets the mode of the pointer controller. */ + virtual void setPresentation(Presentation presentation) = 0; + + /* Sets the spots for the current gesture. + * The spots are not subject to the inactivity timeout like the pointer + * itself it since they are expected to remain visible for so long as + * the fingers are on the touch pad. + * + * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant. + * For spotCoords, pressure != 0 indicates that the spot's location is being + * pressed (not hovering). + */ + virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, + BitSet32 spotIdBits) = 0; + + /* Removes all spots. */ + virtual void clearSpots() = 0; +}; + +} // namespace android + +#endif // _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H diff --git a/services/inputflinger/tests/Android.mk b/services/inputflinger/tests/Android.mk new file mode 100644 index 000000000..6dae82f40 --- /dev/null +++ b/services/inputflinger/tests/Android.mk @@ -0,0 +1,51 @@ +# Build the unit tests. +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# Build the unit tests. +test_src_files := \ + InputReader_test.cpp \ + InputDispatcher_test.cpp + +shared_libraries := \ + libcutils \ + liblog \ + libandroidfw \ + libutils \ + libhardware \ + libhardware_legacy \ + libui \ + libskia \ + libstlport \ + libinput \ + libinputflinger \ + libinputservice + +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_CFLAGS += -Wno-unused-parameter) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval LOCAL_MODULE_TAGS := $(module_tags)) \ + $(eval include $(BUILD_NATIVE_TEST)) \ +) + +# Build the manual test programs. +include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp new file mode 100644 index 000000000..26b4fab79 --- /dev/null +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -0,0 +1,247 @@ +/* + * 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. + */ + +#include "../InputDispatcher.h" + +#include +#include + +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 { + InputDispatcherConfiguration mConfig; + +protected: + virtual ~FakeInputDispatcherPolicy() { + } + +public: + FakeInputDispatcherPolicy() { + } + +private: + virtual void notifyConfigurationChanged(nsecs_t when) { + } + + virtual nsecs_t notifyANR(const sp& inputApplicationHandle, + const sp& inputWindowHandle, + const String8& reason) { + return 0; + } + + virtual void notifyInputChannelBroken(const sp& inputWindowHandle) { + } + + virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) { + *outConfig = mConfig; + } + + virtual bool isKeyRepeatEnabled() { + return true; + } + + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) { + return true; + } + + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { + } + + virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) { + } + + virtual nsecs_t interceptKeyBeforeDispatching(const sp& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags) { + return 0; + } + + virtual bool dispatchUnhandledKey(const sp& inputWindowHandle, + const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) { + return false; + } + + virtual void notifySwitch(nsecs_t when, + uint32_t switchValues, uint32_t switchMask, 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 mFakePolicy; + sp 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, 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, 0)) + << "Should reject key events with ACTION_MULTIPLE."; +} + +TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { + MotionEvent event; + PointerProperties pointerProperties[MAX_POINTERS + 1]; + PointerCoords pointerCoords[MAX_POINTERS + 1]; + for (int i = 0; i <= MAX_POINTERS; i++) { + pointerProperties[i].clear(); + pointerProperties[i].id = i; + pointerCoords[i].clear(); + } + + // Rejects undefined motion actions. + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + /*action*/ -1, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 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, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 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, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 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, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 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, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 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, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 0, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 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, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) + << "Should reject motion events with more than MAX_POINTERS pointers."; + + // Rejects motion events with invalid pointer ids. + pointerProperties[0].id = -1; + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) + << "Should reject motion events with pointer ids less than 0."; + + pointerProperties[0].id = MAX_POINTER_ID + 1; + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 1, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) + << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; + + // Rejects motion events with duplicate pointer ids. + pointerProperties[0].id = 1; + pointerProperties[1].id = 1; + event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0, + ARBITRARY_TIME, ARBITRARY_TIME, + /*pointerCount*/ 2, pointerProperties, pointerCoords); + ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) + << "Should reject motion events with duplicate pointer ids."; +} + +} // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp new file mode 100644 index 000000000..aaa973db4 --- /dev/null +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -0,0 +1,5099 @@ +/* + * 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. + */ + +#include "../InputReader.h" + +#include +#include +#include + +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 +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; + float mX, mY; + int32_t mButtonState; + +protected: + virtual ~FakePointerController() { } + +public: + FakePointerController() : + mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mX(0), mY(0), + mButtonState(0) { + } + + void setBounds(float minX, float minY, float maxX, float maxY) { + mHaveBounds = true; + mMinX = minX; + mMinY = minY; + mMaxX = maxX; + mMaxY = maxY; + } + + virtual void setPosition(float x, float y) { + mX = x; + mY = y; + } + + virtual void setButtonState(int32_t buttonState) { + mButtonState = buttonState; + } + + virtual int32_t getButtonState() const { + return mButtonState; + } + + virtual void getPosition(float* outX, float* outY) const { + *outX = mX; + *outY = mY; + } + +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) { + mX += deltaX; + if (mX < mMinX) mX = mMinX; + if (mX > mMaxX) mX = mMaxX; + mY += deltaY; + if (mY < mMinY) mY = mMinY; + if (mY > mMaxY) mY = mMaxY; + } + + virtual void fade(Transition transition) { + } + + virtual void unfade(Transition transition) { + } + + virtual void setPresentation(Presentation presentation) { + } + + virtual void setSpots(const PointerCoords* spotCoords, + const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { + } + + virtual void clearSpots() { + } +}; + + +// --- FakeInputReaderPolicy --- + +class FakeInputReaderPolicy : public InputReaderPolicyInterface { + InputReaderConfiguration mConfig; + KeyedVector > mPointerControllers; + Vector mInputDevices; + +protected: + virtual ~FakeInputReaderPolicy() { } + +public: + FakeInputReaderPolicy() { + } + + void setDisplayInfo(int32_t displayId, int32_t width, int32_t height, int32_t orientation) { + // Set the size of both the internal and external display at the same time. + bool isRotated = (orientation == DISPLAY_ORIENTATION_90 + || orientation == DISPLAY_ORIENTATION_270); + DisplayViewport v; + v.displayId = displayId; + v.orientation = orientation; + v.logicalLeft = 0; + v.logicalTop = 0; + v.logicalRight = isRotated ? height : width; + v.logicalBottom = isRotated ? width : height; + v.physicalLeft = 0; + v.physicalTop = 0; + v.physicalRight = isRotated ? height : width; + v.physicalBottom = isRotated ? width : height; + v.deviceWidth = isRotated ? height : width; + v.deviceHeight = isRotated ? width : height; + mConfig.setDisplayInfo(false /*external*/, v); + mConfig.setDisplayInfo(true /*external*/, v); + } + + void addExcludedDeviceName(const String8& deviceName) { + mConfig.excludedDeviceNames.push(deviceName); + } + + void setPointerController(int32_t deviceId, const sp& controller) { + mPointerControllers.add(deviceId, controller); + } + + const InputReaderConfiguration* getReaderConfiguration() const { + return &mConfig; + } + + const Vector& getInputDevices() const { + return mInputDevices; + } + +private: + virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) { + *outConfig = mConfig; + } + + virtual sp obtainPointerController(int32_t deviceId) { + return mPointerControllers.valueFor(deviceId); + } + + virtual void notifyInputDevicesChanged(const Vector& inputDevices) { + mInputDevices = inputDevices; + } + + virtual sp getKeyboardLayoutOverlay(const InputDeviceIdentifier& identifier) { + return NULL; + } + + virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) { + return String8::empty(); + } +}; + + +// --- FakeInputListener --- + +class FakeInputListener : public InputListenerInterface { +private: + List mNotifyConfigurationChangedArgsQueue; + List mNotifyDeviceResetArgsQueue; + List mNotifyKeyArgsQueue; + List mNotifyMotionArgsQueue; + List mNotifySwitchArgsQueue; + +protected: + virtual ~FakeInputListener() { } + +public: + FakeInputListener() { + } + + void assertNotifyConfigurationChangedWasCalled( + NotifyConfigurationChangedArgs* outEventArgs = NULL) { + ASSERT_FALSE(mNotifyConfigurationChangedArgsQueue.empty()) + << "Expected notifyConfigurationChanged() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyConfigurationChangedArgsQueue.begin(); + } + mNotifyConfigurationChangedArgsQueue.erase(mNotifyConfigurationChangedArgsQueue.begin()); + } + + void assertNotifyDeviceResetWasCalled( + NotifyDeviceResetArgs* outEventArgs = NULL) { + ASSERT_FALSE(mNotifyDeviceResetArgsQueue.empty()) + << "Expected notifyDeviceReset() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyDeviceResetArgsQueue.begin(); + } + mNotifyDeviceResetArgsQueue.erase(mNotifyDeviceResetArgsQueue.begin()); + } + + void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = NULL) { + ASSERT_FALSE(mNotifyKeyArgsQueue.empty()) + << "Expected notifyKey() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyKeyArgsQueue.begin(); + } + mNotifyKeyArgsQueue.erase(mNotifyKeyArgsQueue.begin()); + } + + void assertNotifyKeyWasNotCalled() { + ASSERT_TRUE(mNotifyKeyArgsQueue.empty()) + << "Expected notifyKey() to not have been called."; + } + + void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = NULL) { + ASSERT_FALSE(mNotifyMotionArgsQueue.empty()) + << "Expected notifyMotion() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyMotionArgsQueue.begin(); + } + mNotifyMotionArgsQueue.erase(mNotifyMotionArgsQueue.begin()); + } + + void assertNotifyMotionWasNotCalled() { + ASSERT_TRUE(mNotifyMotionArgsQueue.empty()) + << "Expected notifyMotion() to not have been called."; + } + + void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = NULL) { + ASSERT_FALSE(mNotifySwitchArgsQueue.empty()) + << "Expected notifySwitch() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifySwitchArgsQueue.begin(); + } + mNotifySwitchArgsQueue.erase(mNotifySwitchArgsQueue.begin()); + } + +private: + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { + mNotifyConfigurationChangedArgsQueue.push_back(*args); + } + + virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) { + mNotifyDeviceResetArgsQueue.push_back(*args); + } + + virtual void notifyKey(const NotifyKeyArgs* args) { + mNotifyKeyArgsQueue.push_back(*args); + } + + virtual void notifyMotion(const NotifyMotionArgs* args) { + mNotifyMotionArgsQueue.push_back(*args); + } + + virtual void notifySwitch(const NotifySwitchArgs* args) { + mNotifySwitchArgsQueue.push_back(*args); + } +}; + + +// --- FakeEventHub --- + +class FakeEventHub : public EventHubInterface { + struct KeyInfo { + int32_t keyCode; + uint32_t flags; + }; + + struct Device { + InputDeviceIdentifier identifier; + uint32_t classes; + PropertyMap configuration; + KeyedVector absoluteAxes; + KeyedVector relativeAxes; + KeyedVector keyCodeStates; + KeyedVector scanCodeStates; + KeyedVector switchStates; + KeyedVector absoluteAxisValue; + KeyedVector keysByScanCode; + KeyedVector keysByUsageCode; + KeyedVector leds; + Vector virtualKeys; + + Device(uint32_t classes) : + classes(classes) { + } + }; + + KeyedVector mDevices; + Vector mExcludedDevices; + List 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(classes); + device->identifier.name = name; + mDevices.add(deviceId, device); + + enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); + } + + void removeDevice(int32_t deviceId) { + delete mDevices.valueFor(deviceId); + mDevices.removeItem(deviceId); + + enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); + } + + void finishDeviceScan() { + enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 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 addAbsoluteAxis(int32_t deviceId, int axis, + int32_t minValue, int32_t maxValue, int flat, int fuzz, int resolution = 0) { + Device* device = getDevice(deviceId); + + RawAbsoluteAxisInfo info; + info.valid = true; + info.minValue = minValue; + info.maxValue = maxValue; + info.flat = flat; + info.fuzz = fuzz; + info.resolution = resolution; + device->absoluteAxes.add(axis, info); + } + + void addRelativeAxis(int32_t deviceId, int32_t axis) { + Device* device = getDevice(deviceId); + device->relativeAxes.add(axis, true); + } + + 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 setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { + Device* device = getDevice(deviceId); + device->absoluteAxisValue.replaceValueFor(axis, value); + } + + void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, + int32_t keyCode, uint32_t flags) { + Device* device = getDevice(deviceId); + KeyInfo info; + info.keyCode = keyCode; + info.flags = flags; + if (scanCode) { + device->keysByScanCode.add(scanCode, info); + } + if (usageCode) { + device->keysByUsageCode.add(usageCode, 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& 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 code, int32_t value) { + RawEvent event; + event.when = when; + event.deviceId = deviceId; + event.type = type; + event.code = code; + event.value = value; + mEvents.push_back(event); + + if (type == EV_ABS) { + setAbsoluteAxisValue(deviceId, code, value); + } + } + + 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 InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->identifier : InputDeviceIdentifier(); + } + + virtual int32_t getDeviceControllerNumber(int32_t deviceId) const { + return 0; + } + + 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->absoluteAxes.indexOfKey(axis); + if (index >= 0) { + *outAxisInfo = device->absoluteAxes.valueAt(index); + return OK; + } + } + outAxisInfo->clear(); + return -1; + } + + virtual bool hasRelativeAxis(int32_t deviceId, int axis) const { + Device* device = getDevice(deviceId); + if (device) { + return device->relativeAxes.indexOfKey(axis) >= 0; + } + return false; + } + + virtual bool hasInputProperty(int32_t deviceId, int property) const { + return false; + } + + virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, + int32_t* outKeycode, uint32_t* outFlags) const { + Device* device = getDevice(deviceId); + if (device) { + const KeyInfo* key = getKey(device, scanCode, usageCode); + if (key) { + if (outKeycode) { + *outKeycode = key->keyCode; + } + if (outFlags) { + *outFlags = key->flags; + } + return OK; + } + } + return NAME_NOT_FOUND; + } + + const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const { + if (usageCode) { + ssize_t index = device->keysByUsageCode.indexOfKey(usageCode); + if (index >= 0) { + return &device->keysByUsageCode.valueAt(index); + } + } + if (scanCode) { + ssize_t index = device->keysByScanCode.indexOfKey(scanCode); + if (index >= 0) { + return &device->keysByScanCode.valueAt(index); + } + } + return NULL; + } + + virtual status_t mapAxis(int32_t deviceId, int32_t scanCode, + AxisInfo* outAxisInfo) const { + return NAME_NOT_FOUND; + } + + virtual void setExcludedDevices(const Vector& devices) { + mExcludedDevices = devices; + } + + virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { + if (mEvents.empty()) { + return 0; + } + + *buffer = *mEvents.begin(); + mEvents.erase(mEvents.begin()); + return 1; + } + + 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 status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->absoluteAxisValue.indexOfKey(axis); + if (index >= 0) { + *outValue = device->absoluteAxisValue.valueAt(index); + return OK; + } + } + *outValue = 0; + return -1; + } + + 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->keysByScanCode.size(); j++) { + if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { + outFlags[i] = 1; + result = true; + } + } + for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { + if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) { + outFlags[i] = 1; + result = true; + } + } + } + } + return result; + } + + virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->keysByScanCode.indexOfKey(scanCode); + return index >= 0; + } + return false; + } + + 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& outVirtualKeys) const { + outVirtualKeys.clear(); + + Device* device = getDevice(deviceId); + if (device) { + outVirtualKeys.appendVector(device->virtualKeys); + } + } + + virtual sp getKeyCharacterMap(int32_t deviceId) const { + return NULL; + } + + virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp& map) { + return false; + } + + virtual void vibrate(int32_t deviceId, nsecs_t duration) { + } + + virtual void cancelVibrate(int32_t deviceId) { + } + + virtual bool isExternal(int32_t deviceId) const { + return false; + } + + virtual void dump(String8& dump) { + } + + virtual void monitor() { + } + + virtual void requestReopenDevices() { + } + + virtual void wake() { + } +}; + + +// --- FakeInputReaderContext --- + +class FakeInputReaderContext : public InputReaderContext { + sp mEventHub; + sp mPolicy; + sp mListener; + int32_t mGlobalMetaState; + bool mUpdateGlobalMetaStateWasCalled; + int32_t mGeneration; + +public: + FakeInputReaderContext(const sp& eventHub, + const sp& policy, + const sp& listener) : + mEventHub(eventHub), mPolicy(policy), mListener(listener), + 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 InputListenerInterface* getListener() { + return mListener.get(); + } + + virtual void disableVirtualKeysUntil(nsecs_t time) { + } + + virtual bool shouldDropVirtualKey(nsecs_t now, + InputDevice* device, int32_t keyCode, int32_t scanCode) { + return false; + } + + virtual void fadePointer() { + } + + virtual void requestTimeoutAtTime(nsecs_t when) { + } + + virtual int32_t bumpGeneration() { + return ++mGeneration; + } +}; + + +// --- FakeInputMapper --- + +class FakeInputMapper : public InputMapper { + uint32_t mSources; + int32_t mKeyboardType; + int32_t mMetaState; + KeyedVector mKeyCodeStates; + KeyedVector mScanCodeStates; + KeyedVector mSwitchStates; + Vector 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(nsecs_t when, + const InputReaderConfiguration* config, uint32_t changes) { + mConfigureWasCalled = true; + } + + virtual void reset(nsecs_t when) { + 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; + } + + virtual void fadePointer() { + } +}; + + +// --- InstrumentedInputReader --- + +class InstrumentedInputReader : public InputReader { + InputDevice* mNextDevice; + +public: + InstrumentedInputReader(const sp& eventHub, + const sp& policy, + const sp& listener) : + InputReader(eventHub, policy, listener), + mNextDevice(NULL) { + } + + virtual ~InstrumentedInputReader() { + if (mNextDevice) { + delete mNextDevice; + } + } + + void setNextDevice(InputDevice* device) { + mNextDevice = device; + } + + InputDevice* newDevice(int32_t deviceId, int32_t controllerNumber, const String8& name, + uint32_t classes) { + InputDeviceIdentifier identifier; + identifier.name = name; + int32_t generation = deviceId + 1; + return new InputDevice(&mContext, deviceId, generation, controllerNumber, identifier, + classes); + } + +protected: + virtual InputDevice* createDeviceLocked(int32_t deviceId, int32_t controllerNumber, + const InputDeviceIdentifier& identifier, uint32_t classes) { + if (mNextDevice) { + InputDevice* device = mNextDevice; + mNextDevice = NULL; + return device; + } + return InputReader::createDeviceLocked(deviceId, controllerNumber, identifier, classes); + } + + friend class InputReaderTest; +}; + + +// --- InputReaderTest --- + +class InputReaderTest : public testing::Test { +protected: + sp mFakeListener; + sp mFakePolicy; + sp mFakeEventHub; + sp mReader; + + virtual void SetUp() { + mFakeEventHub = new FakeEventHub(); + mFakePolicy = new FakeInputReaderPolicy(); + mFakeListener = new FakeInputListener(); + + mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeListener); + } + + virtual void TearDown() { + mReader.clear(); + + mFakeListener.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, int32_t controllerNumber, + const String8& name, uint32_t classes, uint32_t sources, + const PropertyMap* configuration) { + InputDevice* device = mReader->newDevice(deviceId, controllerNumber, name, classes); + FakeInputMapper* mapper = new FakeInputMapper(device, sources); + device->addMapper(mapper); + mReader->setNextDevice(device); + addDevice(deviceId, name, classes, configuration); + return mapper; + } +}; + +TEST_F(InputReaderTest, GetInputDevices) { + ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"), + INPUT_DEVICE_CLASS_KEYBOARD, NULL)); + ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("ignored"), + 0, NULL)); // no classes so device will be ignored + + Vector inputDevices; + mReader->getInputDevices(inputDevices); + + ASSERT_EQ(1U, inputDevices.size()); + ASSERT_EQ(1, inputDevices[0].getId()); + ASSERT_STREQ("keyboard", inputDevices[0].getIdentifier().name.string()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources()); + ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size()); + + // Should also have received a notification describing the new input devices. + inputDevices = mFakePolicy->getInputDevices(); + ASSERT_EQ(1U, inputDevices.size()); + ASSERT_EQ(1, inputDevices[0].getId()); + ASSERT_STREQ("keyboard", inputDevices[0].getIdentifier().name.string()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources()); + ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size()); +} + +TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) { + FakeInputMapper* mapper = NULL; + ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, 0, 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, 0, 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, 0, 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, 0, 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); + + NotifyConfigurationChangedArgs args; + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); +} + +TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) { + FakeInputMapper* mapper = NULL; + ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, 0, String8("fake"), + INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL)); + + mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, 1); + 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.code); + ASSERT_EQ(1, event.value); +} + + +// --- InputDeviceTest --- + +class InputDeviceTest : public testing::Test { +protected: + static const char* DEVICE_NAME; + static const int32_t DEVICE_ID; + static const int32_t DEVICE_GENERATION; + static const int32_t DEVICE_CONTROLLER_NUMBER; + static const uint32_t DEVICE_CLASSES; + + sp mFakeEventHub; + sp mFakePolicy; + sp mFakeListener; + FakeInputReaderContext* mFakeContext; + + InputDevice* mDevice; + + virtual void SetUp() { + mFakeEventHub = new FakeEventHub(); + mFakePolicy = new FakeInputReaderPolicy(); + mFakeListener = new FakeInputListener(); + mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener); + + mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0); + InputDeviceIdentifier identifier; + identifier.name = DEVICE_NAME; + mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION, + DEVICE_CONTROLLER_NUMBER, identifier, DEVICE_CLASSES); + } + + virtual void TearDown() { + delete mDevice; + + delete mFakeContext; + mFakeListener.clear(); + mFakePolicy.clear(); + mFakeEventHub.clear(); + } +}; + +const char* InputDeviceTest::DEVICE_NAME = "device"; +const int32_t InputDeviceTest::DEVICE_ID = 1; +const int32_t InputDeviceTest::DEVICE_GENERATION = 2; +const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0; +const uint32_t InputDeviceTest::DEVICE_CLASSES = INPUT_DEVICE_CLASS_KEYBOARD + | INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_JOYSTICK; + +TEST_F(InputDeviceTest, ImmutableProperties) { + ASSERT_EQ(DEVICE_ID, mDevice->getId()); + ASSERT_STREQ(DEVICE_NAME, mDevice->getName()); + ASSERT_EQ(DEVICE_CLASSES, mDevice->getClasses()); +} + +TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { + // Configuration. + InputReaderConfiguration config; + mDevice->configure(ARBITRARY_TIME, &config, 0); + + // Reset. + mDevice->reset(ARBITRARY_TIME); + + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + + // 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.getIdentifier().name.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."; +} + +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); + + InputReaderConfiguration config; + mDevice->configure(ARBITRARY_TIME, &config, 0); + + 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()); + + // Reset + mDevice->reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mapper1->assertResetWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2->assertResetWasCalled()); + + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + + // 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.getIdentifier().name.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, 1); + + ASSERT_NO_FATAL_FAILURE(mapper1->assertProcessWasCalled()); + ASSERT_NO_FATAL_FAILURE(mapper2->assertProcessWasCalled()); +} + + +// --- InputMapperTest --- + +class InputMapperTest : public testing::Test { +protected: + static const char* DEVICE_NAME; + static const int32_t DEVICE_ID; + static const int32_t DEVICE_GENERATION; + static const int32_t DEVICE_CONTROLLER_NUMBER; + static const uint32_t DEVICE_CLASSES; + + sp mFakeEventHub; + sp mFakePolicy; + sp mFakeListener; + FakeInputReaderContext* mFakeContext; + InputDevice* mDevice; + + virtual void SetUp() { + mFakeEventHub = new FakeEventHub(); + mFakePolicy = new FakeInputReaderPolicy(); + mFakeListener = new FakeInputListener(); + mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener); + InputDeviceIdentifier identifier; + identifier.name = DEVICE_NAME; + mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION, + DEVICE_CONTROLLER_NUMBER, identifier, DEVICE_CLASSES); + + mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0); + } + + virtual void TearDown() { + delete mDevice; + delete mFakeContext; + mFakeListener.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(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + mDevice->reset(ARBITRARY_TIME); + } + + void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + int32_t orientation) { + mFakePolicy->setDisplayInfo(displayId, width, height, orientation); + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); + } + + static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type, + int32_t code, int32_t value) { + RawEvent event; + event.when = when; + event.deviceId = deviceId; + event.type = type; + event.code = code; + event.value = value; + mapper->process(&event); + } + + static void assertMotionRange(const InputDeviceInfo& info, + int32_t axis, uint32_t source, float min, float max, float flat, float fuzz) { + const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); + ASSERT_TRUE(range != NULL) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source; + } + + static void assertPointerCoords(const PointerCoords& coords, + float x, float y, float pressure, float size, + float touchMajor, float touchMinor, float toolMajor, float toolMinor, + float orientation, float distance) { + ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON); + ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON); + ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), 1); + ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), 1); + ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), 1); + ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), 1); + ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON); + ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON); + } + + static void assertPosition(const sp& controller, float x, float y) { + float actualX, actualY; + controller->getPosition(&actualX, &actualY); + ASSERT_NEAR(x, actualX, 1); + ASSERT_NEAR(y, actualY, 1); + } +}; + +const char* InputMapperTest::DEVICE_NAME = "device"; +const int32_t InputMapperTest::DEVICE_ID = 1; +const int32_t InputMapperTest::DEVICE_GENERATION = 2; +const int32_t InputMapperTest::DEVICE_CONTROLLER_NUMBER = 0; +const uint32_t InputMapperTest::DEVICE_CLASSES = 0; // not needed for current tests + + +// --- SwitchInputMapperTest --- + +class SwitchInputMapperTest : public InputMapperTest { +protected: +}; + +TEST_F(SwitchInputMapperTest, GetSources) { + SwitchInputMapper* mapper = new SwitchInputMapper(mDevice); + addMapperAndConfigure(mapper); + + ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_JACK_PHYSICAL_INSERT, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_HEADPHONE_INSERT, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + + NotifySwitchArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args)); + ASSERT_EQ(ARBITRARY_TIME, args.eventTime); + ASSERT_EQ((1 << SW_LID) | (1 << SW_JACK_PHYSICAL_INSERT), args.switchValues); + ASSERT_EQ((1 << SW_LID) | (1 << SW_JACK_PHYSICAL_INSERT) | (1 << SW_HEADPHONE_INSERT), + args.switchMask); + 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) { + NotifyKeyArgs args; + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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) { + const int32_t USAGE_A = 0x070004; + const int32_t USAGE_UNKNOWN = 0x07ffff; + mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(DEVICE_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE); + + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + // Key down by scan code. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_HOME, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->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 by scan code. + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, + EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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); + + // Key down by usage code. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_MSC, MSC_SCAN, USAGE_A); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, 0, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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_A, args.keyCode); + ASSERT_EQ(0, 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 by usage code. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_MSC, MSC_SCAN, USAGE_A); + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, + EV_KEY, 0, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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_A, args.keyCode); + ASSERT_EQ(0, 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 down with unknown scan code or usage code. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_MSC, MSC_SCAN, USAGE_UNKNOWN); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_UNKNOWN, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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(0, args.keyCode); + ASSERT_EQ(KEY_UNKNOWN, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(0U, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); + + // Key up with unknown scan code or usage code. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_MSC, MSC_SCAN, USAGE_UNKNOWN); + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, + EV_KEY, KEY_UNKNOWN, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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(0, args.keyCode); + ASSERT_EQ(KEY_UNKNOWN, args.scanCode); + ASSERT_EQ(AMETA_NONE, args.metaState); + ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags); + ASSERT_EQ(0U, args.policyFlags); + ASSERT_EQ(ARBITRARY_TIME, args.downTime); +} + +TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) { + mFakeEventHub->addKey(DEVICE_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_A, 0, AKEYCODE_A, 0); + + 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, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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) { + mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); + + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + setDisplayInfoAndReconfigure(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) { + mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); + + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addConfigurationProperty("keyboard.orientationAware", "1"); + addMapperAndConfigure(mapper); + + setDisplayInfoAndReconfigure(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)); + + setDisplayInfoAndReconfigure(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)); + + setDisplayInfoAndReconfigure(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)); + + setDisplayInfoAndReconfigure(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. + NotifyKeyArgs args; + + setDisplayInfoAndReconfigure(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_270); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); + ASSERT_EQ(KEY_UP, args.scanCode); + ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); + + setDisplayInfoAndReconfigure(DISPLAY_ID, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_180); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0, 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*/); + mFakeEventHub->addKey(DEVICE_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); + mFakeEventHub->addKey(DEVICE_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + + 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, 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 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) { + NotifyMotionArgs args; + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, originalX); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, originalY); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 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, AINPUT_SOURCE_MOUSE)); + ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 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 - 1, 480 - 1); + + InputDeviceInfo info2; + mapper->populateDeviceInfo(&info2); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, + 1, 800 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, + 2, 480 - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, + 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, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL, + -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, + AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL, + 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); + + 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, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); + 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, 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); + process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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.buttonState); + ASSERT_EQ(0, args.edgeFlags); + ASSERT_EQ(uint32_t(1), args.pointerCount); + ASSERT_EQ(0, args.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); + 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, 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); + + NotifyMotionArgs args; + + // Motion in X but not Y. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0.0f)); + + // Motion in Y but not X. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + 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, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + NotifyMotionArgs args; + + // Button press. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0.0f)); + + // Button release. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + NotifyMotionArgs args; + + // Combined X, Y and Button. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0.0f)); + + // Move X, Y a bit while pressed. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 2); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0.0f)); + + // Release Button. + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 0.0f)); +} + +TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateMotions) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "navigation"); + addMapperAndConfigure(mapper); + + setDisplayInfoAndReconfigure(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); + + setDisplayInfoAndReconfigure(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)); + + setDisplayInfoAndReconfigure(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)); + + setDisplayInfoAndReconfigure(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)); + + setDisplayInfoAndReconfigure(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)); +} + +TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "pointer"); + addMapperAndConfigure(mapper); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + mFakePointerController->setButtonState(0); + + NotifyMotionArgs motionArgs; + NotifyKeyArgs keyArgs; + + // press BTN_LEFT, release BTN_LEFT + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState()); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + mFakePointerController->getButtonState()); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + // press BTN_BACK, release BTN_BACK + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_SIDE, release BTN_SIDE + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_FORWARD, release BTN_FORWARD + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + // press BTN_EXTRA, release BTN_EXTRA + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(0, mFakePointerController->getButtonState()); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); +} + +TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) { + CursorInputMapper* mapper = new CursorInputMapper(mDevice); + addConfigurationProperty("cursor.mode", "pointer"); + addMapperAndConfigure(mapper); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + mFakePointerController->setButtonState(0); + + NotifyMotionArgs args; + + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertPosition(mFakePointerController, 110.0f, 220.0f)); +} + + +// --- 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_DISTANCE_MIN; + static const int32_t RAW_DISTANCE_MAX; + static const int32_t RAW_TILT_MIN; + static const int32_t RAW_TILT_MAX; + static const int32_t RAW_ID_MIN; + static const int32_t RAW_ID_MAX; + static const int32_t RAW_SLOT_MIN; + static const int32_t RAW_SLOT_MAX; + static const float X_PRECISION; + static const float Y_PRECISION; + + static const float GEOMETRIC_SCALE; + + 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, + DISTANCE = 1 << 7, + TILT = 1 << 8, + SLOT = 1 << 9, + TOOL_TYPE = 1 << 10, + }; + + 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 = 1019; +const int32_t TouchInputMapperTest::RAW_Y_MIN = 30; +const int32_t TouchInputMapperTest::RAW_Y_MAX = 1009; +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_DISTANCE_MIN = 0; +const int32_t TouchInputMapperTest::RAW_DISTANCE_MAX = 7; +const int32_t TouchInputMapperTest::RAW_TILT_MIN = 0; +const int32_t TouchInputMapperTest::RAW_TILT_MAX = 150; +const int32_t TouchInputMapperTest::RAW_ID_MIN = 0; +const int32_t TouchInputMapperTest::RAW_ID_MAX = 9; +const int32_t TouchInputMapperTest::RAW_SLOT_MIN = 0; +const int32_t TouchInputMapperTest::RAW_SLOT_MAX = 9; +const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH; +const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT; + +const float TouchInputMapperTest::GEOMETRIC_SCALE = + avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN + 1), + float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN + 1)); + +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) { + setDisplayInfoAndReconfigure(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, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); + mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, 0, AKEYCODE_MENU, POLICY_FLAG_WAKE); +} + +int32_t TouchInputMapperTest::toRawX(float displayX) { + return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH + RAW_X_MIN); +} + +int32_t TouchInputMapperTest::toRawY(float displayY) { + return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / 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 + 1); +} + +float TouchInputMapperTest::toDisplayY(int32_t rawY) { + return float(rawY - RAW_Y_MIN) * DISPLAY_HEIGHT / (RAW_Y_MAX - RAW_Y_MIN + 1); +} + + +// --- SingleTouchInputMapperTest --- + +class SingleTouchInputMapperTest : public TouchInputMapperTest { +protected: + void prepareButtons(); + 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 processDistance(SingleTouchInputMapper* mapper, int32_t distance); + void processTilt(SingleTouchInputMapper* mapper, int32_t tiltX, int32_t tiltY); + void processKey(SingleTouchInputMapper* mapper, int32_t code, int32_t value); + void processSync(SingleTouchInputMapper* mapper); +}; + +void SingleTouchInputMapperTest::prepareButtons() { + mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); +} + +void SingleTouchInputMapperTest::prepareAxes(int axes) { + if (axes & POSITION) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_X, + RAW_X_MIN, RAW_X_MAX, 0, 0); + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_Y, + RAW_Y_MIN, RAW_Y_MAX, 0, 0); + } + if (axes & PRESSURE) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_PRESSURE, + RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0); + } + if (axes & TOOL) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_TOOL_WIDTH, + RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0); + } + if (axes & DISTANCE) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_DISTANCE, + RAW_DISTANCE_MIN, RAW_DISTANCE_MAX, 0, 0); + } + if (axes & TILT) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_TILT_X, + RAW_TILT_MIN, RAW_TILT_MAX, 0, 0); + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_TILT_Y, + RAW_TILT_MIN, RAW_TILT_MAX, 0, 0); + } +} + +void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 1); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y); +} + +void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y); +} + +void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0); +} + +void SingleTouchInputMapperTest::processPressure( + SingleTouchInputMapper* mapper, int32_t pressure) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, pressure); +} + +void SingleTouchInputMapperTest::processToolMajor( + SingleTouchInputMapper* mapper, int32_t toolMajor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, toolMajor); +} + +void SingleTouchInputMapperTest::processDistance( + SingleTouchInputMapper* mapper, int32_t distance) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_DISTANCE, distance); +} + +void SingleTouchInputMapperTest::processTilt( + SingleTouchInputMapper* mapper, int32_t tiltX, int32_t tiltY) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_X, tiltX); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_Y, tiltY); +} + +void SingleTouchInputMapperTest::processKey( + SingleTouchInputMapper* mapper, int32_t code, int32_t value) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value); +} + +void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); +} + + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + prepareButtons(); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_X); + mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_Y); + prepareButtons(); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + prepareButtons(); + 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); + prepareButtons(); + prepareAxes(POSITION); + addConfigurationProperty("touch.deviceType", "touchScreen"); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + 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(mFakeListener->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(mFakeListener->assertNotifyKeyWasCalled()); + + ASSERT_EQ(AKEY_STATE_UP, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME)); +} + +TEST_F(SingleTouchInputMapperTest, GetScanCodeState) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + 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(mFakeListener->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(mFakeListener->assertNotifyKeyWasCalled()); + + ASSERT_EQ(AKEY_STATE_UP, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME)); +} + +TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + 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, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + 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(mFakeListener->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(mFakeListener->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(mFakeListener->assertNotifyKeyWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + 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(mFakeListener->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(mFakeListener->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); + + NotifyMotionArgs motionArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + 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(mFakeListener->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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + NotifyMotionArgs motionArgs; + + // Down. + int32_t x = 100; + int32_t y = 125; + processDown(mapper, x, y); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x), toDisplayY(y), 1, 0, 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(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_DoesNotRotateMotions) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareButtons(); + prepareAxes(POSITION); + addConfigurationProperty("touch.orientationAware", "0"); + addMapperAndConfigure(mapper); + + NotifyMotionArgs args; + + // Rotation 90. + prepareDisplay(DISPLAY_ORIENTATION_90); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_RotatesMotions) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareButtons(); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + NotifyMotionArgs args; + + // Rotation 0. + prepareDisplay(DISPLAY_ORIENTATION_0); + processDown(mapper, toRawX(50), toRawY(75)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + + // Rotation 90. + prepareDisplay(DISPLAY_ORIENTATION_90); + processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50)); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + + // Rotation 180. + prepareDisplay(DISPLAY_ORIENTATION_180); + processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + + // Rotation 270. + prepareDisplay(DISPLAY_ORIENTATION_270); + processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); +} + +TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT); + 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; + int32_t rawDistance = 2; + int32_t rawTiltX = 30; + int32_t rawTiltY = 110; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float pressure = float(rawPressure) / RAW_PRESSURE_MAX; + float size = float(rawToolMajor) / RAW_TOOL_MAX; + float tool = float(rawToolMajor) * GEOMETRIC_SCALE; + float distance = float(rawDistance); + + float tiltCenter = (RAW_TILT_MAX + RAW_TILT_MIN) * 0.5f; + float tiltScale = M_PI / 180; + float tiltXAngle = (rawTiltX - tiltCenter) * tiltScale; + float tiltYAngle = (rawTiltY - tiltCenter) * tiltScale; + float orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)); + float tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle)); + + processDown(mapper, rawX, rawY); + processPressure(mapper, rawPressure); + processToolMajor(mapper, rawToolMajor); + processDistance(mapper, rawDistance); + processTilt(mapper, rawTiltX, rawTiltY); + processSync(mapper); + + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, size, tool, tool, tool, tool, orientation, distance)); + ASSERT_EQ(tilt, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TILT)); +} + +TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + NotifyKeyArgs keyArgs; + + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + + // press BTN_LEFT, release BTN_LEFT + processKey(mapper, BTN_LEFT, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + + processKey(mapper, BTN_LEFT, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + processKey(mapper, BTN_RIGHT, 1); + processKey(mapper, BTN_MIDDLE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + + processKey(mapper, BTN_RIGHT, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_MIDDLE, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // press BTN_BACK, release BTN_BACK + processKey(mapper, BTN_BACK, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_BACK, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_SIDE, release BTN_SIDE + processKey(mapper, BTN_SIDE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_SIDE, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_FORWARD, release BTN_FORWARD + processKey(mapper, BTN_FORWARD, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_FORWARD, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + // press BTN_EXTRA, release BTN_EXTRA + processKey(mapper, BTN_EXTRA, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_EXTRA, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + // press BTN_STYLUS, release BTN_STYLUS + processKey(mapper, BTN_STYLUS, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY, motionArgs.buttonState); + + processKey(mapper, BTN_STYLUS, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // press BTN_STYLUS2, release BTN_STYLUS2 + processKey(mapper, BTN_STYLUS2, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + + processKey(mapper, BTN_STYLUS2, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // release touch + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); +} + +TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + + // default tool type is finger + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // eraser + processKey(mapper, BTN_TOOL_RUBBER, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + + // stylus + processKey(mapper, BTN_TOOL_RUBBER, 0); + processKey(mapper, BTN_TOOL_PEN, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // brush + processKey(mapper, BTN_TOOL_PEN, 0); + processKey(mapper, BTN_TOOL_BRUSH, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // pencil + processKey(mapper, BTN_TOOL_BRUSH, 0); + processKey(mapper, BTN_TOOL_PENCIL, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // airbrush + processKey(mapper, BTN_TOOL_PENCIL, 0); + processKey(mapper, BTN_TOOL_AIRBRUSH, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // mouse + processKey(mapper, BTN_TOOL_AIRBRUSH, 0); + processKey(mapper, BTN_TOOL_MOUSE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + + // lens + processKey(mapper, BTN_TOOL_MOUSE, 0); + processKey(mapper, BTN_TOOL_LENS, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + + // double-tap + processKey(mapper, BTN_TOOL_LENS, 0); + processKey(mapper, BTN_TOOL_DOUBLETAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // triple-tap + processKey(mapper, BTN_TOOL_DOUBLETAP, 0); + processKey(mapper, BTN_TOOL_TRIPLETAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // quad-tap + processKey(mapper, BTN_TOOL_TRIPLETAP, 0); + processKey(mapper, BTN_TOOL_QUADTAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // finger + processKey(mapper, BTN_TOOL_QUADTAP, 0); + processKey(mapper, BTN_TOOL_FINGER, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // stylus trumps finger + processKey(mapper, BTN_TOOL_PEN, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // eraser trumps stylus + processKey(mapper, BTN_TOOL_RUBBER, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + + // mouse trumps eraser + processKey(mapper, BTN_TOOL_MOUSE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + + // back to default tool type + processKey(mapper, BTN_TOOL_MOUSE, 0); + processKey(mapper, BTN_TOOL_RUBBER, 0); + processKey(mapper, BTN_TOOL_PEN, 0); + processKey(mapper, BTN_TOOL_FINGER, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + mFakeEventHub->addKey(DEVICE_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + + // initially hovering because BTN_TOUCH not sent yet, pressure defaults to 0 + processKey(mapper, BTN_TOOL_FINGER, 1); + processMove(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + // move a little + processMove(mapper, 150, 250); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // down when BTN_TOUCH is pressed, pressure defaults to 1 + processKey(mapper, BTN_TOUCH, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + // up when BTN_TOUCH is released, hover restored + processKey(mapper, BTN_TOUCH, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // exit hover when pointer goes away + processKey(mapper, BTN_TOOL_FINGER, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsValueIsZero) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION | PRESSURE); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + + // initially hovering because pressure is 0 + processDown(mapper, 100, 200); + processPressure(mapper, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + // move a little + processMove(mapper, 150, 250); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // down when pressure is non-zero + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + // up when pressure becomes 0, hover restored + processPressure(mapper, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // exit hover when pointer goes away + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 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 processDistance(MultiTouchInputMapper* mapper, int32_t distance); + void processId(MultiTouchInputMapper* mapper, int32_t id); + void processSlot(MultiTouchInputMapper* mapper, int32_t slot); + void processToolType(MultiTouchInputMapper* mapper, int32_t toolType); + void processKey(MultiTouchInputMapper* mapper, int32_t code, int32_t value); + void processMTSync(MultiTouchInputMapper* mapper); + void processSync(MultiTouchInputMapper* mapper); +}; + +void MultiTouchInputMapperTest::prepareAxes(int axes) { + if (axes & POSITION) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_POSITION_X, + RAW_X_MIN, RAW_X_MAX, 0, 0); + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_POSITION_Y, + RAW_Y_MIN, RAW_Y_MAX, 0, 0); + } + if (axes & TOUCH) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOUCH_MAJOR, + RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0); + if (axes & MINOR) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOUCH_MINOR, + RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0); + } + } + if (axes & TOOL) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_WIDTH_MAJOR, + RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0); + if (axes & MINOR) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_WIDTH_MINOR, + RAW_TOOL_MAX, RAW_TOOL_MAX, 0, 0); + } + } + if (axes & ORIENTATION) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_ORIENTATION, + RAW_ORIENTATION_MIN, RAW_ORIENTATION_MAX, 0, 0); + } + if (axes & PRESSURE) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_PRESSURE, + RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0); + } + if (axes & DISTANCE) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_DISTANCE, + RAW_DISTANCE_MIN, RAW_DISTANCE_MAX, 0, 0); + } + if (axes & ID) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TRACKING_ID, + RAW_ID_MIN, RAW_ID_MAX, 0, 0); + } + if (axes & SLOT) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_SLOT, + RAW_SLOT_MIN, RAW_SLOT_MAX, 0, 0); + mFakeEventHub->setAbsoluteAxisValue(DEVICE_ID, ABS_MT_SLOT, 0); + } + if (axes & TOOL_TYPE) { + mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOOL_TYPE, + 0, MT_TOOL_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, x); + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, y); +} + +void MultiTouchInputMapperTest::processTouchMajor( + MultiTouchInputMapper* mapper, int32_t touchMajor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, touchMajor); +} + +void MultiTouchInputMapperTest::processTouchMinor( + MultiTouchInputMapper* mapper, int32_t touchMinor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, touchMinor); +} + +void MultiTouchInputMapperTest::processToolMajor( + MultiTouchInputMapper* mapper, int32_t toolMajor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, toolMajor); +} + +void MultiTouchInputMapperTest::processToolMinor( + MultiTouchInputMapper* mapper, int32_t toolMinor) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, toolMinor); +} + +void MultiTouchInputMapperTest::processOrientation( + MultiTouchInputMapper* mapper, int32_t orientation) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, orientation); +} + +void MultiTouchInputMapperTest::processPressure( + MultiTouchInputMapper* mapper, int32_t pressure) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, pressure); +} + +void MultiTouchInputMapperTest::processDistance( + MultiTouchInputMapper* mapper, int32_t distance) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_DISTANCE, distance); +} + +void MultiTouchInputMapperTest::processId( + MultiTouchInputMapper* mapper, int32_t id) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, id); +} + +void MultiTouchInputMapperTest::processSlot( + MultiTouchInputMapper* mapper, int32_t slot) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_SLOT, slot); +} + +void MultiTouchInputMapperTest::processToolType( + MultiTouchInputMapper* mapper, int32_t toolType) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOOL_TYPE, toolType); +} + +void MultiTouchInputMapperTest::processKey( + MultiTouchInputMapper* mapper, int32_t code, int32_t value) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value); +} + +void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0); +} + +void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) { + process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0); +} + + +TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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(mFakeListener->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.buttonState); + ASSERT_EQ(0, motionArgs.edgeFlags); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + 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(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->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(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 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(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 0)); + + // First finger up. + x2 += 15; y2 -= 20; + processPosition(mapper, x2, y2); + processId(mapper, 2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->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(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 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(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 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(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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, 0)); + + // Second finger up. + x3 += 30; y3 -= 20; + processPosition(mapper, x3, y3); + processId(mapper, 3); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); + + // Last finger up. + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT); + prepareVirtualKeys(); + addMapperAndConfigure(mapper); + + mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); + + NotifyMotionArgs motionArgs; + + // Two fingers down at once. + int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500; + processPosition(mapper, x1, y1); + processId(mapper, 1); + processSlot(mapper, 1); + processPosition(mapper, x2, y2); + processId(mapper, 2); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->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(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 0)); + + // Move. + x1 += 10; y1 += 15; x2 += 5; y2 -= 10; + processSlot(mapper, 0); + processPosition(mapper, x1, y1); + processSlot(mapper, 1); + processPosition(mapper, x2, y2); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 0)); + + // First finger up. + x2 += 15; y2 -= 20; + processSlot(mapper, 0); + processId(mapper, -1); + processSlot(mapper, 1); + processPosition(mapper, x2, y2); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->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(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x1), toDisplayY(y1), 1, 0, 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, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); + + // Move. + x2 += 20; y2 -= 25; + processPosition(mapper, x2, y2); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(1, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); + + // New finger down. + int32_t x3 = 700, y3 = 300; + processPosition(mapper, x2, y2); + processSlot(mapper, 0); + processId(mapper, 3); + processPosition(mapper, x3, y3); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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, 0)); + + // Second finger up. + x3 += 30; y3 -= 20; + processSlot(mapper, 1); + processId(mapper, -1); + processSlot(mapper, 0); + processPosition(mapper, x3, y3); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + motionArgs.action); + ASSERT_EQ(size_t(2), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1, motionArgs.pointerProperties[1].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 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, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); + + // Last finger up. + processId(mapper, -1); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(size_t(1), motionArgs.pointerCount); + ASSERT_EQ(0, motionArgs.pointerProperties[0].id); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); + + // Should not have sent any more keys or motions. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR | DISTANCE); + 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 rawDistance = 0; + 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(rawTouchMajor, rawTouchMinor) / RAW_TOUCH_MAX; + float toolMajor = float(rawToolMajor) * GEOMETRIC_SCALE; + float toolMinor = float(rawToolMinor) * GEOMETRIC_SCALE; + float touchMajor = float(rawTouchMajor) * GEOMETRIC_SCALE; + float touchMinor = float(rawTouchMinor) * GEOMETRIC_SCALE; + float orientation = float(rawOrientation) / RAW_ORIENTATION_MAX * M_PI_2; + float distance = float(rawDistance); + + processPosition(mapper, rawX, rawY); + processTouchMajor(mapper, rawTouchMajor); + processTouchMinor(mapper, rawTouchMinor); + processToolMajor(mapper, rawToolMajor); + processToolMinor(mapper, rawToolMinor); + processPressure(mapper, rawPressure); + processOrientation(mapper, rawOrientation); + processDistance(mapper, rawDistance); + processId(mapper, id); + processMTSync(mapper); + processSync(mapper); + + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(0, args.pointerProperties[0].id); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, + orientation, distance)); +} + +TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL | MINOR); + addConfigurationProperty("touch.size.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 size = avg(rawTouchMajor, rawTouchMinor) / RAW_TOUCH_MAX; + float toolMajor = float(rawToolMajor) * GEOMETRIC_SCALE; + float toolMinor = float(rawToolMinor) * GEOMETRIC_SCALE; + float touchMajor = float(rawTouchMajor) * GEOMETRIC_SCALE; + float touchMinor = float(rawTouchMinor) * GEOMETRIC_SCALE; + + processPosition(mapper, rawX, rawY); + processTouchMajor(mapper, rawTouchMajor); + processTouchMinor(mapper, rawTouchMinor); + processToolMajor(mapper, rawToolMajor); + processToolMinor(mapper, rawToolMinor); + processMTSync(mapper); + processSync(mapper); + + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, 1.0f, size, touchMajor, touchMinor, toolMajor, toolMinor, 0, 0)); +} + +TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL); + addConfigurationProperty("touch.size.calibration", "diameter"); + addConfigurationProperty("touch.size.scale", "10"); + addConfigurationProperty("touch.size.bias", "160"); + addConfigurationProperty("touch.size.isSummed", "1"); + 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 = 5; + int32_t rawToolMajor = 8; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float x2 = toDisplayX(rawX2); + float y2 = toDisplayY(rawY2); + float size = float(rawTouchMajor) / 2 / RAW_TOUCH_MAX; + float touch = float(rawTouchMajor) / 2 * 10.0f + 160.0f; + float tool = float(rawToolMajor) / 2 * 10.0f + 160.0f; + + 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); + + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->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, 1.0f, size, touch, touch, tool, tool, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[1], + x2, y2, 1.0f, size, touch, touch, tool, tool, 0, 0)); +} + +TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | TOUCH | TOOL); + addConfigurationProperty("touch.size.calibration", "area"); + addConfigurationProperty("touch.size.scale", "43"); + addConfigurationProperty("touch.size.bias", "3"); + addMapperAndConfigure(mapper); + + // These calculations are based on the input device calibration documentation. + int32_t rawX = 100; + int32_t rawY = 200; + int32_t rawTouchMajor = 5; + int32_t rawToolMajor = 8; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float size = float(rawTouchMajor) / RAW_TOUCH_MAX; + float touch = sqrtf(rawTouchMajor) * 43.0f + 3.0f; + float tool = sqrtf(rawToolMajor) * 43.0f + 3.0f; + + processPosition(mapper, rawX, rawY); + processTouchMajor(mapper, rawTouchMajor); + processToolMajor(mapper, rawToolMajor); + processMTSync(mapper); + processSync(mapper); + + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, 1.0f, size, touch, touch, tool, tool, 0, 0)); +} + +TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | PRESSURE); + addConfigurationProperty("touch.pressure.calibration", "amplitude"); + 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 rawPressure = 60; + + float x = toDisplayX(rawX); + float y = toDisplayY(rawY); + float pressure = float(rawPressure) * 0.01f; + + processPosition(mapper, rawX, rawY); + processPressure(mapper, rawPressure); + processMTSync(mapper); + processSync(mapper); + + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], + x, y, pressure, 0, 0, 0, 0, 0, 0, 0)); +} + +TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + NotifyKeyArgs keyArgs; + + processId(mapper, 1); + processPosition(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + + // press BTN_LEFT, release BTN_LEFT + processKey(mapper, BTN_LEFT, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + + processKey(mapper, BTN_LEFT, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + processKey(mapper, BTN_RIGHT, 1); + processKey(mapper, BTN_MIDDLE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + + processKey(mapper, BTN_RIGHT, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_MIDDLE, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // press BTN_BACK, release BTN_BACK + processKey(mapper, BTN_BACK, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_BACK, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_SIDE, release BTN_SIDE + processKey(mapper, BTN_SIDE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_SIDE, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_FORWARD, release BTN_FORWARD + processKey(mapper, BTN_FORWARD, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_FORWARD, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + // press BTN_EXTRA, release BTN_EXTRA + processKey(mapper, BTN_EXTRA, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + processKey(mapper, BTN_EXTRA, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + // press BTN_STYLUS, release BTN_STYLUS + processKey(mapper, BTN_STYLUS, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY, motionArgs.buttonState); + + processKey(mapper, BTN_STYLUS, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // press BTN_STYLUS2, release BTN_STYLUS2 + processKey(mapper, BTN_STYLUS2, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + + processKey(mapper, BTN_STYLUS2, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + + // release touch + processId(mapper, -1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); +} + +TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + + // default tool type is finger + processId(mapper, 1); + processPosition(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // eraser + processKey(mapper, BTN_TOOL_RUBBER, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + + // stylus + processKey(mapper, BTN_TOOL_RUBBER, 0); + processKey(mapper, BTN_TOOL_PEN, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // brush + processKey(mapper, BTN_TOOL_PEN, 0); + processKey(mapper, BTN_TOOL_BRUSH, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // pencil + processKey(mapper, BTN_TOOL_BRUSH, 0); + processKey(mapper, BTN_TOOL_PENCIL, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // airbrush + processKey(mapper, BTN_TOOL_PENCIL, 0); + processKey(mapper, BTN_TOOL_AIRBRUSH, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // mouse + processKey(mapper, BTN_TOOL_AIRBRUSH, 0); + processKey(mapper, BTN_TOOL_MOUSE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + + // lens + processKey(mapper, BTN_TOOL_MOUSE, 0); + processKey(mapper, BTN_TOOL_LENS, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + + // double-tap + processKey(mapper, BTN_TOOL_LENS, 0); + processKey(mapper, BTN_TOOL_DOUBLETAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // triple-tap + processKey(mapper, BTN_TOOL_DOUBLETAP, 0); + processKey(mapper, BTN_TOOL_TRIPLETAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // quad-tap + processKey(mapper, BTN_TOOL_TRIPLETAP, 0); + processKey(mapper, BTN_TOOL_QUADTAP, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // finger + processKey(mapper, BTN_TOOL_QUADTAP, 0); + processKey(mapper, BTN_TOOL_FINGER, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // stylus trumps finger + processKey(mapper, BTN_TOOL_PEN, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // eraser trumps stylus + processKey(mapper, BTN_TOOL_RUBBER, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + + // mouse trumps eraser + processKey(mapper, BTN_TOOL_MOUSE, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + + // MT tool type trumps BTN tool types: MT_TOOL_FINGER + processToolType(mapper, MT_TOOL_FINGER); // this is the first time we send MT_TOOL_TYPE + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + + // MT tool type trumps BTN tool types: MT_TOOL_PEN + processToolType(mapper, MT_TOOL_PEN); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + + // back to default tool type + processToolType(mapper, -1); // use a deliberately undefined tool type, for testing + processKey(mapper, BTN_TOOL_MOUSE, 0); + processKey(mapper, BTN_TOOL_RUBBER, 0); + processKey(mapper, BTN_TOOL_PEN, 0); + processKey(mapper, BTN_TOOL_FINGER, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); +} + +TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT); + mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + + // initially hovering because BTN_TOUCH not sent yet, pressure defaults to 0 + processId(mapper, 1); + processPosition(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + // move a little + processPosition(mapper, 150, 250); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // down when BTN_TOUCH is pressed, pressure defaults to 1 + processKey(mapper, BTN_TOUCH, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + // up when BTN_TOUCH is released, hover restored + processKey(mapper, BTN_TOUCH, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // exit hover when pointer goes away + processId(mapper, -1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); +} + +TEST_F(MultiTouchInputMapperTest, Process_WhenAbsMTPressureIsPresent_HoversIfItsValueIsZero) { + MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + addMapperAndConfigure(mapper); + + NotifyMotionArgs motionArgs; + + // initially hovering because pressure is 0 + processId(mapper, 1); + processPosition(mapper, 100, 200); + processPressure(mapper, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0)); + + // move a little + processPosition(mapper, 150, 250); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // down when pressure becomes non-zero + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + // up when pressure becomes 0, hover restored + processPressure(mapper, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); + + // exit hover when pointer goes away + processId(mapper, -1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], + toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); +} + + +} // namespace android