diff options
author | Jeff Brown <jeffbrown@google.com> | 2010-04-22 18:58:52 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2010-06-13 17:42:16 -0700 |
commit | e839a589bf582568cf220c1040ed93b948e6e362 (patch) | |
tree | c5e5d1126fb0e65a4d054c9411484eff23cb943c /libs/ui | |
parent | f7b5648c220e14f3b1b8c055a6feb36ae95896bc (diff) | |
download | frameworks_native-e839a589bf582568cf220c1040ed93b948e6e362.zip frameworks_native-e839a589bf582568cf220c1040ed93b948e6e362.tar.gz frameworks_native-e839a589bf582568cf220c1040ed93b948e6e362.tar.bz2 |
Native input dispatch rewrite work in progress.
The old dispatch mechanism has been left in place and continues to
be used by default for now. To enable native input dispatch,
edit the ENABLE_NATIVE_DISPATCH constant in WindowManagerPolicy.
Includes part of the new input event NDK API. Some details TBD.
To wire up input dispatch, as the ViewRoot adds a window to the
window session it receives an InputChannel object as an output
argument. The InputChannel encapsulates the file descriptors for a
shared memory region and two pipe end-points. The ViewRoot then
provides the InputChannel to the InputQueue. Behind the
scenes, InputQueue simply attaches handlers to the native PollLoop object
that underlies the MessageQueue. This way MessageQueue doesn't need
to know anything about input dispatch per-se, it just exposes (in native
code) a PollLoop that other components can use to monitor file descriptor
state changes.
There can be zero or more targets for any given input event. Each
input target is specified by its input channel and some parameters
including flags, an X/Y coordinate offset, and the dispatch timeout.
An input target can request either synchronous dispatch (for foreground apps)
or asynchronous dispatch (fire-and-forget for wallpapers and "outside"
targets). Currently, finding the appropriate input targets for an event
requires a call back into the WindowManagerServer from native code.
In the future this will be refactored to avoid most of these callbacks
except as required to handle pending focus transitions.
End-to-end event dispatch mostly works!
To do: event injection, rate limiting, ANRs, testing, optimization, etc.
Change-Id: I8c36b2b9e0a2d27392040ecda0f51b636456de25
Diffstat (limited to 'libs/ui')
-rw-r--r-- | libs/ui/Android.mk | 5 | ||||
-rw-r--r-- | libs/ui/EventHub.cpp | 172 | ||||
-rw-r--r-- | libs/ui/Input.cpp | 238 | ||||
-rw-r--r-- | libs/ui/InputDispatcher.cpp | 1315 | ||||
-rw-r--r-- | libs/ui/InputManager.cpp | 114 | ||||
-rw-r--r-- | libs/ui/InputReader.cpp | 1844 | ||||
-rw-r--r-- | libs/ui/InputTransport.cpp | 684 | ||||
-rw-r--r-- | libs/ui/tests/Android.mk | 34 | ||||
-rw-r--r-- | libs/ui/tests/InputDispatcher_test.cpp | 19 | ||||
-rw-r--r-- | libs/ui/tests/region/Android.mk | 16 | ||||
-rw-r--r-- | libs/ui/tests/region/region.cpp (renamed from libs/ui/tests/region.cpp) | 0 |
11 files changed, 4361 insertions, 80 deletions
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk index f7acd97..24cdc78 100644 --- a/libs/ui/Android.mk +++ b/libs/ui/Android.mk @@ -11,6 +11,11 @@ LOCAL_SRC_FILES:= \ GraphicBufferMapper.cpp \ KeyLayoutMap.cpp \ KeyCharacterMap.cpp \ + Input.cpp \ + InputDispatcher.cpp \ + InputManager.cpp \ + InputReader.cpp \ + InputTransport.cpp \ IOverlay.cpp \ Overlay.cpp \ PixelFormat.cpp \ diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp index d45eaf0..27895f2 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -155,77 +155,70 @@ int EventHub::getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, return 0; } -int EventHub::getSwitchState(int sw) const -{ -#ifdef EV_SW - if (sw >= 0 && sw <= SW_MAX) { - int32_t devid = mSwitches[sw]; - if (devid != 0) { - return getSwitchState(devid, sw); +int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const { + if (scanCode >= 0 && scanCode <= KEY_MAX) { + AutoMutex _l(mLock); + + if (deviceId == -1) { + for (int i = 0; i < mNumDevicesById; i++) { + device_t* device = mDevicesById[i].device; + if (device != NULL && (device->classes & deviceClasses) != 0) { + int32_t result = getScanCodeStateLocked(device, scanCode); + if (result >= KEY_STATE_DOWN) { + return result; + } + } + } + return KEY_STATE_UP; + } else { + device_t* device = getDevice(deviceId); + if (device != NULL) { + return getScanCodeStateLocked(device, scanCode); + } } } -#endif - return -1; + return KEY_STATE_UNKNOWN; } -int EventHub::getSwitchState(int32_t deviceId, int sw) const -{ -#ifdef EV_SW - AutoMutex _l(mLock); - device_t* device = getDevice(deviceId); - if (device == NULL) return -1; - - if (sw >= 0 && sw <= SW_MAX) { - uint8_t sw_bitmask[(SW_MAX+7)/8]; - memset(sw_bitmask, 0, sizeof(sw_bitmask)); - if (ioctl(mFDs[id_to_index(device->id)].fd, - EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) { - return test_bit(sw, sw_bitmask) ? 1 : 0; - } +int32_t EventHub::getScanCodeStateLocked(device_t* device, int32_t scanCode) const { + uint8_t key_bitmask[(KEY_MAX + 7) / 8]; + memset(key_bitmask, 0, sizeof(key_bitmask)); + if (ioctl(mFDs[id_to_index(device->id)].fd, + EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { + return test_bit(scanCode, key_bitmask) ? KEY_STATE_DOWN : KEY_STATE_UP; } -#endif - - return -1; + return KEY_STATE_UNKNOWN; } -int EventHub::getScancodeState(int code) const -{ - return getScancodeState(mFirstKeyboardId, code); -} +int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const { -int EventHub::getScancodeState(int32_t deviceId, int code) const -{ - AutoMutex _l(mLock); - device_t* device = getDevice(deviceId); - if (device == NULL) return -1; - - if (code >= 0 && code <= KEY_MAX) { - uint8_t key_bitmask[(KEY_MAX+7)/8]; - memset(key_bitmask, 0, sizeof(key_bitmask)); - if (ioctl(mFDs[id_to_index(device->id)].fd, - EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { - return test_bit(code, key_bitmask) ? 1 : 0; + if (deviceId == -1) { + for (int i = 0; i < mNumDevicesById; i++) { + device_t* device = mDevicesById[i].device; + if (device != NULL && (device->classes & deviceClasses) != 0) { + int32_t result = getKeyCodeStateLocked(device, keyCode); + if (result >= KEY_STATE_DOWN) { + return result; + } + } + } + return KEY_STATE_UP; + } else { + device_t* device = getDevice(deviceId); + if (device != NULL) { + return getKeyCodeStateLocked(device, keyCode); } } - - return -1; -} - -int EventHub::getKeycodeState(int code) const -{ - return getKeycodeState(mFirstKeyboardId, code); + return KEY_STATE_UNKNOWN; } -int EventHub::getKeycodeState(int32_t deviceId, int code) const -{ - AutoMutex _l(mLock); - device_t* device = getDevice(deviceId); - if (device == NULL || device->layoutMap == NULL) return -1; - +int32_t EventHub::getKeyCodeStateLocked(device_t* device, int32_t keyCode) const { Vector<int32_t> scanCodes; - device->layoutMap->findScancodes(code, &scanCodes); - - uint8_t key_bitmask[(KEY_MAX+7)/8]; + device->layoutMap->findScancodes(keyCode, &scanCodes); + + uint8_t key_bitmask[(KEY_MAX + 7) / 8]; memset(key_bitmask, 0, sizeof(key_bitmask)); if (ioctl(mFDs[id_to_index(device->id)].fd, EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { @@ -239,12 +232,45 @@ int EventHub::getKeycodeState(int32_t deviceId, int code) const int32_t sc = scanCodes.itemAt(i); //LOGI("Code %d: down=%d", sc, test_bit(sc, key_bitmask)); if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, key_bitmask)) { - return 1; + return KEY_STATE_DOWN; } } + return KEY_STATE_UP; } - - return 0; + return KEY_STATE_UNKNOWN; +} + +int32_t EventHub::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const { +#ifdef EV_SW + if (sw >= 0 && sw <= SW_MAX) { + AutoMutex _l(mLock); + + if (deviceId == -1) { + deviceId = mSwitches[sw]; + if (deviceId == 0) { + return KEY_STATE_UNKNOWN; + } + } + + device_t* device = getDevice(deviceId); + if (device == NULL) { + return KEY_STATE_UNKNOWN; + } + + return getSwitchStateLocked(device, sw); + } +#endif + return KEY_STATE_UNKNOWN; +} + +int32_t EventHub::getSwitchStateLocked(device_t* device, int32_t sw) const { + uint8_t sw_bitmask[(SW_MAX + 7) / 8]; + memset(sw_bitmask, 0, sizeof(sw_bitmask)); + if (ioctl(mFDs[id_to_index(device->id)].fd, + EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) { + return test_bit(sw, sw_bitmask) ? KEY_STATE_DOWN : KEY_STATE_UP; + } + return KEY_STATE_UNKNOWN; } status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode, @@ -309,9 +335,6 @@ bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, status_t err; - fd_set readfds; - int maxFd = -1; - int cc; int i; int res; int pollres; @@ -457,7 +480,7 @@ bool EventHub::openPlatformInput(void) * Inspect the known devices to determine whether physical keys exist for the given * framework-domain key codes. */ -bool EventHub::hasKeys(size_t numCodes, int32_t* keyCodes, uint8_t* outFlags) { +bool EventHub::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { outFlags[codeIndex] = 0; @@ -465,7 +488,8 @@ bool EventHub::hasKeys(size_t numCodes, int32_t* keyCodes, uint8_t* outFlags) { Vector<int32_t> scanCodes; for (int n = 0; (n < mFDCount) && (outFlags[codeIndex] == 0); n++) { if (mDevices[n]) { - status_t err = mDevices[n]->layoutMap->findScancodes(keyCodes[codeIndex], &scanCodes); + status_t err = mDevices[n]->layoutMap->findScancodes( + keyCodes[codeIndex], &scanCodes); if (!err) { // check the possible scan codes identified by the layout map against the // map of codes actually emitted by the driver @@ -618,11 +642,11 @@ int EventHub::open_device(const char *deviceName) //} for (int i=0; i<((BTN_MISC+7)/8); i++) { if (key_bitmask[i] != 0) { - device->classes |= CLASS_KEYBOARD; + device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; break; } } - if ((device->classes & CLASS_KEYBOARD) != 0) { + if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { device->keyBitmask = new uint8_t[sizeof(key_bitmask)]; if (device->keyBitmask != NULL) { memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask)); @@ -642,7 +666,7 @@ int EventHub::open_device(const char *deviceName) if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0) { if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) { - device->classes |= CLASS_TRACKBALL; + device->classes |= INPUT_DEVICE_CLASS_TRACKBALL; } } } @@ -656,12 +680,12 @@ int EventHub::open_device(const char *deviceName) if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask) && test_bit(ABS_MT_POSITION_X, abs_bitmask) && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) { - device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT; + device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT; // Is this an old style single-touch driver? } else if (test_bit(BTN_TOUCH, key_bitmask) && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) { - device->classes |= CLASS_TOUCHSCREEN; + device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN; } #ifdef EV_SW @@ -680,7 +704,7 @@ int EventHub::open_device(const char *deviceName) } #endif - if ((device->classes&CLASS_KEYBOARD) != 0) { + if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { char tmpfn[sizeof(name)]; char keylayoutFilename[300]; @@ -723,7 +747,7 @@ int EventHub::open_device(const char *deviceName) // 'Q' key support = cheap test of whether this is an alpha-capable kbd if (hasKeycode(device, kKeyCodeQ)) { - device->classes |= CLASS_ALPHAKEY; + device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY; } // See if this has a DPAD. @@ -732,7 +756,7 @@ int EventHub::open_device(const char *deviceName) hasKeycode(device, kKeyCodeDpadLeft) && hasKeycode(device, kKeyCodeDpadRight) && hasKeycode(device, kKeyCodeDpadCenter)) { - device->classes |= CLASS_DPAD; + device->classes |= INPUT_DEVICE_CLASS_DPAD; } LOGI("New keyboard: device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n", diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp new file mode 100644 index 0000000..d367708 --- /dev/null +++ b/libs/ui/Input.cpp @@ -0,0 +1,238 @@ +// +// Copyright 2010 The Android Open Source Project +// +// Provides a pipe-based transport for native events in the NDK. +// +#define LOG_TAG "Input" + +//#define LOG_NDEBUG 0 + +#include <ui/Input.h> + +namespace android { + +// class InputEvent + +void InputEvent::initialize(int32_t deviceId, int32_t nature) { + mDeviceId = deviceId; + mNature = nature; +} + +// class KeyEvent + +void KeyEvent::initialize( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t flags, + int32_t keyCode, + int32_t scanCode, + int32_t metaState, + int32_t repeatCount, + nsecs_t downTime, + nsecs_t eventTime) { + InputEvent::initialize(deviceId, nature); + mAction = action; + mFlags = flags; + mKeyCode = keyCode; + mScanCode = scanCode; + mMetaState = metaState; + mRepeatCount = repeatCount; + mDownTime = downTime; + mEventTime = eventTime; +} + +// class MotionEvent + +void MotionEvent::initialize( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t edgeFlags, + int32_t metaState, + float rawX, + float rawY, + float xPrecision, + float yPrecision, + nsecs_t downTime, + nsecs_t eventTime, + size_t pointerCount, + const int32_t* pointerIds, + const PointerCoords* pointerCoords) { + InputEvent::initialize(deviceId, nature); + mAction = action; + mEdgeFlags = edgeFlags; + mMetaState = metaState; + mRawX = rawX; + mRawY = rawY; + mXPrecision = xPrecision; + mYPrecision = yPrecision; + mDownTime = downTime; + mPointerIds.clear(); + mPointerIds.appendArray(pointerIds, pointerCount); + mSampleEventTimes.clear(); + mSamplePointerCoords.clear(); + addSample(eventTime, pointerCoords); +} + +void MotionEvent::addSample( + int64_t eventTime, + const PointerCoords* pointerCoords) { + mSampleEventTimes.push(eventTime); + mSamplePointerCoords.appendArray(pointerCoords, getPointerCount()); +} + +void MotionEvent::offsetLocation(float xOffset, float yOffset) { + if (xOffset != 0 || yOffset != 0) { + for (size_t i = 0; i < mSamplePointerCoords.size(); i++) { + PointerCoords& pointerCoords = mSamplePointerCoords.editItemAt(i); + pointerCoords.x += xOffset; + pointerCoords.y += yOffset; + } + } +} + +} // namespace android + +// NDK APIs + +using android::InputEvent; +using android::KeyEvent; +using android::MotionEvent; + +int32_t input_event_get_type(const input_event_t* event) { + return reinterpret_cast<const InputEvent*>(event)->getType(); +} + +int32_t input_event_get_device_id(const input_event_t* event) { + return reinterpret_cast<const InputEvent*>(event)->getDeviceId(); +} + +int32_t input_event_get_nature(const input_event_t* event) { + return reinterpret_cast<const InputEvent*>(event)->getNature(); +} + +int32_t key_event_get_action(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getAction(); +} + +int32_t key_event_get_flags(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getFlags(); +} + +int32_t key_event_get_key_code(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getKeyCode(); +} + +int32_t key_event_get_scan_code(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getScanCode(); +} + +int32_t key_event_get_meta_state(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getMetaState(); +} +int32_t key_event_get_repeat_count(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getRepeatCount(); +} + +int64_t key_event_get_down_time(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getDownTime(); +} + +int64_t key_event_get_event_time(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getEventTime(); +} + +int32_t motion_event_get_action(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getAction(); +} + +int32_t motion_event_get_meta_state(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getMetaState(); +} + +int32_t motion_event_get_edge_flags(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getEdgeFlags(); +} + +int64_t motion_event_get_down_time(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getDownTime(); +} + +int64_t motion_event_get_event_time(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getEventTime(); +} + +float motion_event_get_x_precision(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getXPrecision(); +} + +float motion_event_get_y_precision(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getYPrecision(); +} + +size_t motion_event_get_pointer_count(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerCount(); +} + +int32_t motion_event_get_pointer_id(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerId(pointer_index); +} + +float motion_event_get_raw_x(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX(); +} + +float motion_event_get_raw_y(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY(); +} + +float motion_event_get_x(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getX(pointer_index); +} + +float motion_event_get_y(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getY(pointer_index); +} + +float motion_event_get_pressure(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getPressure(pointer_index); +} + +float motion_event_get_size(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getSize(pointer_index); +} + +size_t motion_event_get_history_size(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistorySize(); +} + +int64_t motion_event_get_historical_event_time(input_event_t* motion_event, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalEventTime( + history_index); +} + +float motion_event_get_historical_x(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalX( + pointer_index, history_index); +} + +float motion_event_get_historical_y(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalY( + pointer_index, history_index); +} + +float motion_event_get_historical_pressure(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalPressure( + pointer_index, history_index); +} + +float motion_event_get_historical_size(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalSize( + pointer_index, history_index); +} diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp new file mode 100644 index 0000000..8e907da --- /dev/null +++ b/libs/ui/InputDispatcher.cpp @@ -0,0 +1,1315 @@ +// +// Copyright 2010 The Android Open Source Project +// +// The input dispatcher. +// +#define LOG_TAG "InputDispatcher" + +//#define LOG_NDEBUG 0 + +// Log detailed debug messages about each inbound event notification to the dispatcher. +#define DEBUG_INBOUND_EVENT_DETAILS 1 + +// Log detailed debug messages about each outbound event processed by the dispatcher. +#define DEBUG_OUTBOUND_EVENT_DETAILS 1 + +// Log debug messages about batching. +#define DEBUG_BATCHING 1 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 1 + +// Log debug messages about performance statistics. +#define DEBUG_PERFORMANCE_STATISTICS 1 + +#include <cutils/log.h> +#include <ui/InputDispatcher.h> + +#include <stddef.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <limits.h> +#include <poll.h> + +namespace android { + +// TODO, this needs to be somewhere else, perhaps in the policy +static inline bool isMovementKey(int32_t keyCode) { + return keyCode == KEYCODE_DPAD_UP + || keyCode == KEYCODE_DPAD_DOWN + || keyCode == KEYCODE_DPAD_LEFT + || keyCode == KEYCODE_DPAD_RIGHT; +} + +// --- InputDispatcher --- + +InputDispatcher::InputDispatcher(const sp<InputDispatchPolicyInterface>& policy) : + mPolicy(policy) { + mPollLoop = new PollLoop(); + + mInboundQueue.head.refCount = -1; + mInboundQueue.head.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.head.eventTime = LONG_LONG_MIN; + + mInboundQueue.tail.refCount = -1; + mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.tail.eventTime = LONG_LONG_MAX; + + mKeyRepeatState.lastKeyEntry = NULL; +} + +InputDispatcher::~InputDispatcher() { + resetKeyRepeatLocked(); + + while (mConnectionsByReceiveFd.size() != 0) { + unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel); + } + + for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) { + EventEntry* next = entry->next; + mAllocator.releaseEventEntry(next); + entry = next; + } +} + +void InputDispatcher::dispatchOnce() { + bool allowKeyRepeat = mPolicy->allowKeyRepeat(); + + nsecs_t currentTime; + nsecs_t nextWakeupTime = LONG_LONG_MAX; + { // acquire lock + AutoMutex _l(mLock); + currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + + // 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. + // XXX we should handle resetting input state coming out of sleep more generally elsewhere + if (! allowKeyRepeat) { + resetKeyRepeatLocked(); + } + + // Process timeouts for all connections and determine if there are any synchronous + // event dispatches pending. + bool hasPendingSyncTarget = false; + for (size_t i = 0; i < mActiveConnections.size(); ) { + Connection* connection = mActiveConnections.itemAt(i); + + nsecs_t connectionTimeoutTime = connection->nextTimeoutTime; + if (connectionTimeoutTime <= currentTime) { + bool deactivated = timeoutDispatchCycleLocked(currentTime, connection); + if (deactivated) { + // Don't increment i because the connection has been removed + // from mActiveConnections (hence, deactivated). + continue; + } + } + + if (connectionTimeoutTime < nextWakeupTime) { + nextWakeupTime = connectionTimeoutTime; + } + + if (connection->hasPendingSyncTarget()) { + hasPendingSyncTarget = true; + } + + i += 1; + } + + // If we don't have a pending sync target, then we can begin delivering a new event. + // (Otherwise we wait for dispatch to complete for that target.) + if (! hasPendingSyncTarget) { + if (mInboundQueue.isEmpty()) { + if (mKeyRepeatState.lastKeyEntry) { + if (currentTime >= mKeyRepeatState.nextRepeatTime) { + processKeyRepeatLocked(currentTime); + return; // dispatched once + } else { + if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) { + nextWakeupTime = mKeyRepeatState.nextRepeatTime; + } + } + } + } else { + // Inbound queue has at least one entry. Dequeue it and begin dispatching. + // Note that we do not hold the lock for this process because dispatching may + // involve making many callbacks. + EventEntry* entry = mInboundQueue.dequeueAtHead(); + + switch (entry->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: { + ConfigurationChangedEntry* typedEntry = + static_cast<ConfigurationChangedEntry*>(entry); + processConfigurationChangedLocked(currentTime, typedEntry); + mAllocator.releaseConfigurationChangedEntry(typedEntry); + break; + } + + case EventEntry::TYPE_KEY: { + KeyEntry* typedEntry = static_cast<KeyEntry*>(entry); + processKeyLocked(currentTime, typedEntry); + mAllocator.releaseKeyEntry(typedEntry); + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* typedEntry = static_cast<MotionEntry*>(entry); + processMotionLocked(currentTime, typedEntry); + mAllocator.releaseMotionEntry(typedEntry); + break; + } + + default: + assert(false); + break; + } + return; // dispatched once + } + } + } // release lock + + // Wait for callback or timeout or wake. + nsecs_t timeout = nanoseconds_to_milliseconds(nextWakeupTime - currentTime); + int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0; + mPollLoop->pollOnce(timeoutMillis); +} + +void InputDispatcher::processConfigurationChangedLocked(nsecs_t currentTime, + ConfigurationChangedEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, " + "keyboardConfig=%d, navigationConfig=%d", entry->eventTime, + entry->touchScreenConfig, entry->keyboardConfig, entry->navigationConfig); +#endif + + mPolicy->notifyConfigurationChanged(entry->eventTime, entry->touchScreenConfig, + entry->keyboardConfig, entry->navigationConfig); +} + +void InputDispatcher::processKeyLocked(nsecs_t currentTime, KeyEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processKey - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, " + "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld", + entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, entry->action, + entry->flags, entry->keyCode, entry->scanCode, entry->metaState, + entry->downTime); +#endif + + // TODO: Poke user activity. + + if (entry->action == KEY_EVENT_ACTION_DOWN) { + 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 + mPolicy->getKeyRepeatTimeout(); + } + mKeyRepeatState.lastKeyEntry = entry; + entry->refCount += 1; + } else { + resetKeyRepeatLocked(); + } + + identifyInputTargetsAndDispatchKeyLocked(currentTime, entry); +} + +void InputDispatcher::processKeyRepeatLocked(nsecs_t currentTime) { + // TODO Old WindowManagerServer code sniffs the input queue for following key up + // events and drops the repeat if one is found. We should do something similar. + // One good place to do it is in notifyKey as soon as the key up enters the + // inbound event queue. + + // Synthesize a key repeat after the repeat timeout expired. + // We reuse the previous key entry if otherwise unreferenced. + KeyEntry* entry = mKeyRepeatState.lastKeyEntry; + if (entry->refCount == 1) { + entry->repeatCount += 1; + } else { + KeyEntry* newEntry = mAllocator.obtainKeyEntry(); + newEntry->deviceId = entry->deviceId; + newEntry->nature = entry->nature; + newEntry->policyFlags = entry->policyFlags; + newEntry->action = entry->action; + newEntry->flags = entry->flags; + newEntry->keyCode = entry->keyCode; + newEntry->scanCode = entry->scanCode; + newEntry->metaState = entry->metaState; + newEntry->repeatCount = entry->repeatCount + 1; + + mKeyRepeatState.lastKeyEntry = newEntry; + mAllocator.releaseKeyEntry(entry); + + entry = newEntry; + } + entry->eventTime = currentTime; + entry->downTime = currentTime; + entry->policyFlags = 0; + + mKeyRepeatState.nextRepeatTime = currentTime + mPolicy->getKeyRepeatTimeout(); + +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, " + "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, " + "repeatCount=%d, downTime=%lld", + entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, + entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState, + entry->repeatCount, entry->downTime); +#endif + + identifyInputTargetsAndDispatchKeyLocked(currentTime, entry); +} + +void InputDispatcher::processMotionLocked(nsecs_t currentTime, MotionEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processMotion - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, " + "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld", + entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, entry->action, + entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision, + entry->downTime); + + // Print the most recent sample that we have available, this may change due to batching. + size_t sampleCount = 1; + MotionSample* sample = & entry->firstSample; + for (; sample->next != NULL; sample = sample->next) { + sampleCount += 1; + } + for (uint32_t i = 0; i < entry->pointerCount; i++) { + LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f", + i, entry->pointerIds[i], + sample->pointerCoords[i].x, + sample->pointerCoords[i].y, + sample->pointerCoords[i].pressure, + sample->pointerCoords[i].size); + } + + // Keep in mind that due to batching, it is possible for the number of samples actually + // dispatched to change before the application finally consumed them. + if (entry->action == MOTION_EVENT_ACTION_MOVE) { + LOGD(" ... Total movement samples currently batched %d ...", sampleCount); + } +#endif + + identifyInputTargetsAndDispatchMotionLocked(currentTime, entry); +} + +void InputDispatcher::identifyInputTargetsAndDispatchKeyLocked( + nsecs_t currentTime, KeyEntry* entry) { +#if DEBUG_DISPATCH_CYCLE + LOGD("identifyInputTargetsAndDispatchKey"); +#endif + + mReusableKeyEvent.initialize(entry->deviceId, entry->nature, entry->action, entry->flags, + entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, + entry->downTime, entry->eventTime); + + mCurrentInputTargets.clear(); + mPolicy->getKeyEventTargets(& mReusableKeyEvent, entry->policyFlags, + mCurrentInputTargets); + + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); +} + +void InputDispatcher::identifyInputTargetsAndDispatchMotionLocked( + nsecs_t currentTime, MotionEntry* entry) { +#if DEBUG_DISPATCH_CYCLE + LOGD("identifyInputTargetsAndDispatchMotion"); +#endif + + mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action, + entry->edgeFlags, entry->metaState, + entry->firstSample.pointerCoords[0].x, entry->firstSample.pointerCoords[0].y, + entry->xPrecision, entry->yPrecision, + entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds, + entry->firstSample.pointerCoords); + + mCurrentInputTargets.clear(); + mPolicy->getMotionEventTargets(& mReusableMotionEvent, entry->policyFlags, + mCurrentInputTargets); + + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); +} + +void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, + EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + LOGD("dispatchEventToCurrentInputTargets, " + "resumeWithAppendedMotionSample=%s", + resumeWithAppendedMotionSample ? "true" : "false"); +#endif + + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); + + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey( + inputTarget.inputChannel->getReceivePipeFd()); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + prepareDispatchCycleLocked(currentTime, connection.get(), eventEntry, & inputTarget, + resumeWithAppendedMotionSample); + } else { + LOGW("Framework requested delivery of an input event to channel '%s' but it " + "is not registered with the input dispatcher.", + inputTarget.inputChannel->getName().string()); + } + } +} + +void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, Connection* connection, + EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ prepareDispatchCycle, flags=%d, timeout=%lldns, " + "xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s", + connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout, + inputTarget->xOffset, inputTarget->yOffset, + resumeWithAppendedMotionSample ? "true" : "false"); +#endif + + // Skip this event if the connection status is not normal. + // We don't want to queue outbound events at all if the connection is broken or + // not responding. + if (connection->status != Connection::STATUS_NORMAL) { + LOGV("channel '%s' ~ Dropping event because the channel status is %s", + connection->status == Connection::STATUS_BROKEN ? "BROKEN" : "NOT RESPONDING"); + return; + } + + // Resume the dispatch cycle with a freshly appended motion sample. + // First we check that the last dispatch entry in the outbound queue is for the same + // motion event to which we appended the motion sample. If we find such a dispatch + // entry, and if it is currently in progress then we try to stream the new sample. + bool wasEmpty = connection->outboundQueue.isEmpty(); + + if (! wasEmpty && resumeWithAppendedMotionSample) { + DispatchEntry* motionEventDispatchEntry = + connection->findQueuedDispatchEntryForEvent(eventEntry); + if (motionEventDispatchEntry) { + // If the dispatch entry is not in progress, then we must be busy dispatching an + // earlier event. Not a problem, the motion event is on the outbound queue and will + // be dispatched later. + if (! motionEventDispatchEntry->inProgress) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Not streaming because the motion event has " + "not yet been dispatched. " + "(Waiting for earlier events to be consumed.)", + connection->getInputChannelName()); +#endif + return; + } + + // If the dispatch entry is in progress but it already has a tail of pending + // motion samples, then it must mean that the shared memory buffer filled up. + // Not a problem, when this dispatch cycle is finished, we will eventually start + // a new dispatch cycle to process the tail and that tail includes the newly + // appended motion sample. + if (motionEventDispatchEntry->tailMotionSample) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Not streaming because no new samples can " + "be appended to the motion event in this dispatch cycle. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); +#endif + return; + } + + // The dispatch entry is in progress and is still potentially open for streaming. + // Try to stream the new motion sample. This might fail if the consumer has already + // consumed the motion event (or if the channel is broken). + MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample; + status_t status = connection->inputPublisher.appendMotionSample( + appendedMotionSample->eventTime, appendedMotionSample->pointerCoords); + if (status == OK) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Successfully streamed new motion sample.", + connection->getInputChannelName()); +#endif + return; + } + +#if DEBUG_BATCHING + if (status == NO_MEMORY) { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event because the shared memory buffer is full. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); + } else if (status == status_t(FAILED_TRANSACTION)) { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatchedmove event because the event has already been consumed. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); + } else { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event due to an error, status=%d. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName(), status); + } +#endif + // Failed to stream. Start a new tail of pending motion samples to dispatch + // in the next cycle. + motionEventDispatchEntry->tailMotionSample = appendedMotionSample; + return; + } + } + + // This is a new event. + // Enqueue a new dispatch entry onto the outbound queue for this connection. + DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref + dispatchEntry->targetFlags = inputTarget->flags; + dispatchEntry->xOffset = inputTarget->xOffset; + dispatchEntry->yOffset = inputTarget->yOffset; + dispatchEntry->timeout = inputTarget->timeout; + dispatchEntry->inProgress = false; + dispatchEntry->headMotionSample = NULL; + dispatchEntry->tailMotionSample = NULL; + + // Handle the case where we could not stream a new motion sample because the consumer has + // already consumed the motion event (otherwise the corresponding dispatch entry would + // still be in the outbound queue for this connection). We set the head motion sample + // to the list starting with the newly appended motion sample. + if (resumeWithAppendedMotionSample) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples " + "that cannot be streamed because the motion event has already been consumed.", + connection->getInputChannelName()); +#endif + MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample; + dispatchEntry->headMotionSample = appendedMotionSample; + } + + // Enqueue the dispatch entry. + connection->outboundQueue.enqueueAtTail(dispatchEntry); + + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty) { + activateConnectionLocked(connection); + startDispatchCycleLocked(currentTime, connection); + } +} + +void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, Connection* connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ startDispatchCycle", + connection->getInputChannelName()); +#endif + + assert(connection->status == Connection::STATUS_NORMAL); + assert(! connection->outboundQueue.isEmpty()); + + DispatchEntry* dispatchEntry = connection->outboundQueue.head.next; + assert(! dispatchEntry->inProgress); + + // TODO throttle successive ACTION_MOVE motion events for the same device + // possible implementation could set a brief poll timeout here and resume starting the + // dispatch cycle when elapsed + + // Publish the event. + status_t status; + switch (dispatchEntry->eventEntry->type) { + case EventEntry::TYPE_KEY: { + KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); + + // Apply target flags. + int32_t action = keyEntry->action; + int32_t flags = keyEntry->flags; + if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { + flags |= KEY_EVENT_FLAG_CANCELED; + } + + // Publish the key event. + status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->nature, + action, flags, keyEntry->keyCode, keyEntry->scanCode, + keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, + keyEntry->eventTime); + + if (status) { + LOGE("channel '%s' ~ Could not publish key event, " + "status=%d", connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry); + + // Apply target flags. + int32_t action = motionEntry->action; + if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) { + action = MOTION_EVENT_ACTION_OUTSIDE; + } + if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { + action = MOTION_EVENT_ACTION_CANCEL; + } + + // If headMotionSample is non-NULL, then it points to the first new sample that we + // were unable to dispatch during the previous cycle so we resume dispatching from + // that point in the list of motion samples. + // Otherwise, we just start from the first sample of the motion event. + MotionSample* firstMotionSample = dispatchEntry->headMotionSample; + if (! firstMotionSample) { + firstMotionSample = & motionEntry->firstSample; + } + + // Publish the motion event and the first motion sample. + status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId, + motionEntry->nature, action, motionEntry->edgeFlags, motionEntry->metaState, + dispatchEntry->xOffset, dispatchEntry->yOffset, + motionEntry->xPrecision, motionEntry->yPrecision, + motionEntry->downTime, firstMotionSample->eventTime, + motionEntry->pointerCount, motionEntry->pointerIds, + firstMotionSample->pointerCoords); + + if (status) { + LOGE("channel '%s' ~ Could not publish motion event, " + "status=%d", connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + + // Append additional motion samples. + MotionSample* nextMotionSample = firstMotionSample->next; + for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) { + status = connection->inputPublisher.appendMotionSample( + nextMotionSample->eventTime, nextMotionSample->pointerCoords); + if (status == NO_MEMORY) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will " + "be sent in the next dispatch cycle.", + connection->getInputChannelName()); +#endif + break; + } + if (status != OK) { + LOGE("channel '%s' ~ Could not append motion sample " + "for a reason other than out of memory, status=%d", + connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + } + + // Remember the next motion sample that we could not dispatch, in case we ran out + // of space in the shared memory buffer. + dispatchEntry->tailMotionSample = nextMotionSample; + break; + } + + default: { + assert(false); + } + } + + // Send the dispatch signal. + status = connection->inputPublisher.sendDispatchSignal(); + if (status) { + LOGE("channel '%s' ~ Could not send dispatch signal, status=%d", + connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + + // Record information about the newly started dispatch cycle. + dispatchEntry->inProgress = true; + + connection->lastEventTime = dispatchEntry->eventEntry->eventTime; + connection->lastDispatchTime = currentTime; + + nsecs_t timeout = dispatchEntry->timeout; + connection->nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX; + + // Notify other system components. + onDispatchCycleStartedLocked(currentTime, connection); +} + +void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, Connection* connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ finishDispatchCycle: %01.1fms since event, " + "%01.1fms since dispatch", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime)); +#endif + + if (connection->status == Connection::STATUS_BROKEN) { + return; + } + + // Clear the pending timeout. + connection->nextTimeoutTime = LONG_LONG_MAX; + + if (connection->status == Connection::STATUS_NOT_RESPONDING) { + // Recovering from an ANR. + connection->status = Connection::STATUS_NORMAL; + + // Notify other system components. + onDispatchCycleFinishedLocked(currentTime, connection, true /*recoveredFromANR*/); + } else { + // Normal finish. Not much to do here. + + // Notify other system components. + onDispatchCycleFinishedLocked(currentTime, connection, false /*recoveredFromANR*/); + } + + // Reset the publisher since the event has been consumed. + // We do this now so that the publisher can release some of its internal resources + // while waiting for the next dispatch cycle to begin. + status_t status = connection->inputPublisher.reset(); + if (status) { + LOGE("channel '%s' ~ Could not reset publisher, status=%d", + connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + + // Start the next dispatch cycle for this connection. + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.head.next; + if (dispatchEntry->inProgress) { + // Finish or resume current event in progress. + if (dispatchEntry->tailMotionSample) { + // We have a tail of undispatched motion samples. + // Reuse the same DispatchEntry and start a new cycle. + dispatchEntry->inProgress = false; + dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample; + dispatchEntry->tailMotionSample = NULL; + startDispatchCycleLocked(currentTime, connection); + return; + } + // Finished. + connection->outboundQueue.dequeueAtHead(); + mAllocator.releaseDispatchEntry(dispatchEntry); + } else { + // If the head is not in progress, then we must have already dequeued the in + // progress event, which means we actually aborted it (due to ANR). + // So just start the next event for this connection. + startDispatchCycleLocked(currentTime, connection); + return; + } + } + + // Outbound queue is empty, deactivate the connection. + deactivateConnectionLocked(connection); +} + +bool InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime, Connection* connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ timeoutDispatchCycle", + connection->getInputChannelName()); +#endif + + if (connection->status != Connection::STATUS_NORMAL) { + return false; + } + + // Enter the not responding state. + connection->status = Connection::STATUS_NOT_RESPONDING; + connection->lastANRTime = currentTime; + bool deactivated = abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/); + + // Notify other system components. + onDispatchCycleANRLocked(currentTime, connection); + return deactivated; +} + +bool InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, Connection* connection, + bool broken) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ abortDispatchCycle, broken=%s", + connection->getInputChannelName(), broken ? "true" : "false"); +#endif + + if (connection->status == Connection::STATUS_BROKEN) { + return false; + } + + // Clear the pending timeout. + connection->nextTimeoutTime = LONG_LONG_MAX; + + // Clear the outbound queue. + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead(); + mAllocator.releaseDispatchEntry(dispatchEntry); + } + + // Outbound queue is empty, deactivate the connection. + deactivateConnectionLocked(connection); + + // Handle the case where the connection appears to be unrecoverably broken. + if (broken) { + connection->status = Connection::STATUS_BROKEN; + + // Notify other system components. + onDispatchCycleBrokenLocked(currentTime, connection); + } + return true; /*deactivated*/ +} + +bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) { + InputDispatcher* d = static_cast<InputDispatcher*>(data); + + { // acquire lock + AutoMutex _l(d->mLock); + + ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGE("Received spurious receive callback for unknown input channel. " + "fd=%d, events=0x%x", receiveFd, events); + return false; // remove the callback + } + + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + + sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex); + if (events & (POLLERR | POLLHUP | POLLNVAL)) { + LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. " + "events=0x%x", connection->getInputChannelName(), events); + d->abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/); + return false; // remove the callback + } + + if (! (events & POLLIN)) { + LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", connection->getInputChannelName(), events); + return true; + } + + status_t status = connection->inputPublisher.receiveFinishedSignal(); + if (status) { + LOGE("channel '%s' ~ Failed to receive finished signal. status=%d", + connection->getInputChannelName(), status); + d->abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/); + return false; // remove the callback + } + + d->finishDispatchCycleLocked(currentTime, connection.get()); + return true; + } // release lock +} + +void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime, int32_t touchScreenConfig, + int32_t keyboardConfig, int32_t navigationConfig) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, " + "keyboardConfig=%d, navigationConfig=%d", eventTime, + touchScreenConfig, keyboardConfig, navigationConfig); +#endif + + bool wasEmpty; + { // acquire lock + AutoMutex _l(mLock); + + ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(); + newEntry->eventTime = eventTime; + newEntry->touchScreenConfig = touchScreenConfig; + newEntry->keyboardConfig = keyboardConfig; + newEntry->navigationConfig = navigationConfig; + + wasEmpty = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(newEntry); + } // release lock + + if (wasEmpty) { + mPollLoop->wake(); + } +} + +void InputDispatcher::notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyLidSwitchChanged - eventTime=%lld, open=%s", eventTime, + lidOpen ? "true" : "false"); +#endif + + // Send lid switch notification immediately and synchronously. + mPolicy->notifyLidSwitchChanged(eventTime, lidOpen); +} + +void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime); +#endif + + // Remove movement keys from the queue from most recent to least recent, stopping at the + // first non-movement key. + // TODO: Include a detailed description of why we do this... + + { // acquire lock + AutoMutex _l(mLock); + + for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) { + EventEntry* prev = entry->prev; + + if (entry->type == EventEntry::TYPE_KEY) { + KeyEntry* keyEntry = static_cast<KeyEntry*>(entry); + if (isMovementKey(keyEntry->keyCode)) { + LOGV("Dropping movement key during app switch: keyCode=%d, action=%d", + keyEntry->keyCode, keyEntry->action); + mInboundQueue.dequeue(keyEntry); + mAllocator.releaseKeyEntry(keyEntry); + } else { + // stop at last non-movement key + break; + } + } + + entry = prev; + } + } // release lock +} + +void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t flags, + int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, " + "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld", + eventTime, deviceId, nature, policyFlags, action, flags, + keyCode, scanCode, metaState, downTime); +#endif + + bool wasEmpty; + { // acquire lock + AutoMutex _l(mLock); + + KeyEntry* newEntry = mAllocator.obtainKeyEntry(); + newEntry->eventTime = eventTime; + newEntry->deviceId = deviceId; + newEntry->nature = nature; + newEntry->policyFlags = policyFlags; + newEntry->action = action; + newEntry->flags = flags; + newEntry->keyCode = keyCode; + newEntry->scanCode = scanCode; + newEntry->metaState = metaState; + newEntry->repeatCount = 0; + newEntry->downTime = downTime; + + wasEmpty = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(newEntry); + } // release lock + + if (wasEmpty) { + mPollLoop->wake(); + } +} + +void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, " + "action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, " + "downTime=%lld", + eventTime, deviceId, nature, policyFlags, action, metaState, edgeFlags, + xPrecision, yPrecision, downTime); + for (uint32_t i = 0; i < pointerCount; i++) { + LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f", + i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y, + pointerCoords[i].pressure, pointerCoords[i].size); + } +#endif + + bool wasEmpty; + { // acquire lock + AutoMutex _l(mLock); + + // Attempt batching and streaming of move events. + if (action == MOTION_EVENT_ACTION_MOVE) { + // BATCHING CASE + // + // Try to append a move sample to the tail of the inbound queue for this device. + // Give up if we encounter a non-move motion event for this device since that + // means we cannot append any new samples until a new motion event has started. + for (EventEntry* entry = mInboundQueue.tail.prev; + entry != & mInboundQueue.head; entry = entry->prev) { + if (entry->type != EventEntry::TYPE_MOTION) { + // Keep looking for motion events. + continue; + } + + MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); + if (motionEntry->deviceId != deviceId) { + // Keep looking for this device. + continue; + } + + if (motionEntry->action != MOTION_EVENT_ACTION_MOVE + || motionEntry->pointerCount != pointerCount) { + // Last motion event in the queue for this device is not compatible for + // appending new samples. Stop here. + goto NoBatchingOrStreaming; + } + + // The last motion event is a move and is compatible for appending. + // Do the batching magic and exit. + mAllocator.appendMotionSample(motionEntry, eventTime, pointerCount, pointerCoords); +#if DEBUG_BATCHING + LOGD("Appended motion sample onto batch for most recent " + "motion event for this device in the inbound queue."); +#endif + return; // done + } + + // STREAMING CASE + // + // There is no pending motion event (of any kind) for this device in the inbound queue. + // Search the outbound queues for a synchronously dispatched motion event for this + // device. If found, then we append the new sample to that event and then try to + // push it out to all current targets. It is possible that some targets will already + // have consumed the motion event. This case is automatically handled by the + // logic in prepareDispatchCycleLocked by tracking where resumption takes place. + // + // The reason we look for a synchronously dispatched motion event is because we + // want to be sure that no other motion events have been dispatched since the move. + // It's also convenient because it means that the input targets are still valid. + // This code could be improved to support streaming of asynchronously dispatched + // motion events (which might be significantly more efficient) but it may become + // a little more complicated as a result. + // + // Note: This code crucially depends on the invariant that an outbound queue always + // contains at most one synchronous event and it is always last (but it might + // not be first!). + for (size_t i = 0; i < mActiveConnections.size(); i++) { + Connection* connection = mActiveConnections.itemAt(i); + if (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev; + if (dispatchEntry->targetFlags & InputTarget::FLAG_SYNC) { + if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) { + goto NoBatchingOrStreaming; + } + + MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>( + dispatchEntry->eventEntry); + if (syncedMotionEntry->action != MOTION_EVENT_ACTION_MOVE + || syncedMotionEntry->deviceId != deviceId + || syncedMotionEntry->pointerCount != pointerCount) { + goto NoBatchingOrStreaming; + } + + // Found synced move entry. Append sample and resume dispatch. + mAllocator.appendMotionSample(syncedMotionEntry, eventTime, + pointerCount, pointerCoords); +#if DEBUG_BATCHING + LOGD("Appended motion sample onto batch for most recent synchronously " + "dispatched motion event for this device in the outbound queues."); +#endif + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry, + true /*resumeWithAppendedMotionSample*/); + return; // done! + } + } + } + +NoBatchingOrStreaming:; + } + + // Just enqueue a new motion event. + MotionEntry* newEntry = mAllocator.obtainMotionEntry(); + newEntry->eventTime = eventTime; + newEntry->deviceId = deviceId; + newEntry->nature = nature; + newEntry->policyFlags = policyFlags; + newEntry->action = action; + newEntry->metaState = metaState; + newEntry->edgeFlags = edgeFlags; + newEntry->xPrecision = xPrecision; + newEntry->yPrecision = yPrecision; + newEntry->downTime = downTime; + newEntry->pointerCount = pointerCount; + newEntry->firstSample.eventTime = eventTime; + newEntry->lastSample = & newEntry->firstSample; + for (uint32_t i = 0; i < pointerCount; i++) { + newEntry->pointerIds[i] = pointerIds[i]; + newEntry->firstSample.pointerCoords[i] = pointerCoords[i]; + } + + wasEmpty = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(newEntry); + } // release lock + + if (wasEmpty) { + mPollLoop->wake(); + } +} + +void InputDispatcher::resetKeyRepeatLocked() { + if (mKeyRepeatState.lastKeyEntry) { + mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry); + mKeyRepeatState.lastKeyEntry = NULL; + } +} + +status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) { + int receiveFd; + { // acquire lock + AutoMutex _l(mLock); + + receiveFd = inputChannel->getReceivePipeFd(); + if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) { + LOGW("Attempted to register already registered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = new Connection(inputChannel); + status_t status = connection->initialize(); + if (status) { + LOGE("Failed to initialize input publisher for input channel '%s', status=%d", + inputChannel->getName().string(), status); + return status; + } + + mConnectionsByReceiveFd.add(receiveFd, connection); + } // release lock + + mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); + return OK; +} + +status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) { + int32_t receiveFd; + { // acquire lock + AutoMutex _l(mLock); + + receiveFd = inputChannel->getReceivePipeFd(); + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGW("Attempted to unregister already unregistered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + mConnectionsByReceiveFd.removeItemsAt(connectionIndex); + + connection->status = Connection::STATUS_ZOMBIE; + + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/); + } // release lock + + mPollLoop->removeCallback(receiveFd); + + // Wake the poll loop because removing the connection may have changed the current + // synchronization state. + mPollLoop->wake(); + return OK; +} + +void InputDispatcher::activateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + return; + } + } + mActiveConnections.add(connection); +} + +void InputDispatcher::deactivateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + mActiveConnections.removeAt(i); + return; + } + } +} + +void InputDispatcher::onDispatchCycleStartedLocked(nsecs_t currentTime, Connection* connection) { +} + +void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime, + Connection* connection, bool recoveredFromANR) { + if (recoveredFromANR) { + LOGI("channel '%s' ~ Recovered from ANR. %01.1fms since event, " + "%01.1fms since dispatch, %01.1fms since ANR", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime), + connection->getANRLatencyMillis(currentTime)); + + // TODO tell framework + } +} + +void InputDispatcher::onDispatchCycleANRLocked(nsecs_t currentTime, Connection* connection) { + LOGI("channel '%s' ~ Not responding! %01.1fms since event, %01.1fms since dispatch", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime)); + + // TODO tell framework +} + +void InputDispatcher::onDispatchCycleBrokenLocked(nsecs_t currentTime, Connection* connection) { + LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!", + connection->getInputChannelName()); + + // TODO tell framework +} + +// --- InputDispatcher::Allocator --- + +InputDispatcher::Allocator::Allocator() { +} + +InputDispatcher::ConfigurationChangedEntry* +InputDispatcher::Allocator::obtainConfigurationChangedEntry() { + ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_CONFIGURATION_CHANGED; + return entry; +} + +InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry() { + KeyEntry* entry = mKeyEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_KEY; + return entry; +} + +InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry() { + MotionEntry* entry = mMotionEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_MOTION; + entry->firstSample.next = NULL; + return entry; +} + +InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry( + EventEntry* eventEntry) { + DispatchEntry* entry = mDispatchEntryPool.alloc(); + entry->eventEntry = eventEntry; + eventEntry->refCount += 1; + return entry; +} + +void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) { + switch (entry->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: + releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry)); + break; + case EventEntry::TYPE_KEY: + releaseKeyEntry(static_cast<KeyEntry*>(entry)); + break; + case EventEntry::TYPE_MOTION: + releaseMotionEntry(static_cast<MotionEntry*>(entry)); + break; + default: + assert(false); + break; + } +} + +void InputDispatcher::Allocator::releaseConfigurationChangedEntry( + ConfigurationChangedEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + mConfigurationChangeEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + mKeyEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + freeMotionSampleList(entry->firstSample.next); + mMotionEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) { + releaseEventEntry(entry->eventEntry); + mDispatchEntryPool.free(entry); +} + +void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, + nsecs_t eventTime, int32_t pointerCount, const PointerCoords* pointerCoords) { + MotionSample* sample = mMotionSamplePool.alloc(); + sample->eventTime = eventTime; + for (int32_t i = 0; i < pointerCount; i++) { + sample->pointerCoords[i] = pointerCoords[i]; + } + + sample->next = NULL; + motionEntry->lastSample->next = sample; + motionEntry->lastSample = sample; +} + +void InputDispatcher::Allocator::freeMotionSample(MotionSample* sample) { + mMotionSamplePool.free(sample); +} + +void InputDispatcher::Allocator::freeMotionSampleList(MotionSample* head) { + while (head) { + MotionSample* next = head->next; + mMotionSamplePool.free(head); + head = next; + } +} + +// --- InputDispatcher::Connection --- + +InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) : + status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel), + nextTimeoutTime(LONG_LONG_MAX), + lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX), + lastANRTime(LONG_LONG_MAX) { +} + +InputDispatcher::Connection::~Connection() { +} + +status_t InputDispatcher::Connection::initialize() { + return inputPublisher.initialize(); +} + +InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent( + const EventEntry* eventEntry) const { + for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev; + dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) { + if (dispatchEntry->eventEntry == eventEntry) { + return dispatchEntry; + } + } + return NULL; +} + + +// --- InputDispatcherThread --- + +InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) : + Thread(/*canCallJava*/ true), mDispatcher(dispatcher) { +} + +InputDispatcherThread::~InputDispatcherThread() { +} + +bool InputDispatcherThread::threadLoop() { + mDispatcher->dispatchOnce(); + return true; +} + +} // namespace android diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp new file mode 100644 index 0000000..ab354a5 --- /dev/null +++ b/libs/ui/InputManager.cpp @@ -0,0 +1,114 @@ +// +// Copyright 2010 The Android Open Source Project +// +// The input manager. +// +#define LOG_TAG "InputManager" + +//#define LOG_NDEBUG 0 + +#include <cutils/log.h> +#include <ui/InputManager.h> +#include <ui/InputReader.h> +#include <ui/InputDispatcher.h> + +namespace android { + +InputManager::InputManager(const sp<EventHubInterface>& eventHub, + const sp<InputDispatchPolicyInterface>& policy) : + mEventHub(eventHub), mPolicy(policy) { + mDispatcher = new InputDispatcher(policy); + mReader = new InputReader(eventHub, policy, mDispatcher); + + mDispatcherThread = new InputDispatcherThread(mDispatcher); + mReaderThread = new InputReaderThread(mReader); + + configureExcludedDevices(); +} + +InputManager::~InputManager() { + stop(); +} + +void InputManager::configureExcludedDevices() { + Vector<String8> excludedDeviceNames; + mPolicy->getExcludedDeviceNames(excludedDeviceNames); + + for (size_t i = 0; i < excludedDeviceNames.size(); i++) { + mEventHub->addExcludedDevice(excludedDeviceNames[i]); + } +} + +status_t InputManager::start() { + status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputDispatcher thread due to error %d.", result); + return result; + } + + result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputReader thread due to error %d.", result); + + mDispatcherThread->requestExit(); + return result; + } + + return OK; +} + +status_t InputManager::stop() { + status_t result = mReaderThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputReader thread due to error %d.", result); + } + + result = mDispatcherThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputDispatcher thread due to error %d.", result); + } + + return OK; +} + +status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) { + return mDispatcher->registerInputChannel(inputChannel); +} + +status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) { + return mDispatcher->unregisterInputChannel(inputChannel); +} + +int32_t InputManager::getScanCodeState(int32_t deviceId, int32_t deviceClasses, int32_t scanCode) + const { + int32_t vkKeyCode, vkScanCode; + if (mReader->getCurrentVirtualKey(& vkKeyCode, & vkScanCode)) { + if (vkScanCode == scanCode) { + return KEY_STATE_VIRTUAL; + } + } + + return mEventHub->getScanCodeState(deviceId, deviceClasses, scanCode); +} + +int32_t InputManager::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, int32_t keyCode) + const { + int32_t vkKeyCode, vkScanCode; + if (mReader->getCurrentVirtualKey(& vkKeyCode, & vkScanCode)) { + if (vkKeyCode == keyCode) { + return KEY_STATE_VIRTUAL; + } + } + + return mEventHub->getKeyCodeState(deviceId, deviceClasses, keyCode); +} + +int32_t InputManager::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const { + return mEventHub->getSwitchState(deviceId, deviceClasses, sw); +} + +bool InputManager::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { + return mEventHub->hasKeys(numCodes, keyCodes, outFlags); +} + +} // namespace android diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp new file mode 100644 index 0000000..76f9ec9 --- /dev/null +++ b/libs/ui/InputReader.cpp @@ -0,0 +1,1844 @@ +// +// Copyright 2010 The Android Open Source Project +// +// The input reader. +// +#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 1 + +// Log debug messages about virtual key processing. +#define DEBUG_VIRTUAL_KEYS 1 + +// Log debug messages about pointers. +#define DEBUG_POINTERS 1 + +#include <cutils/log.h> +#include <ui/InputReader.h> + +#include <stddef.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <limits.h> + +namespace android { + +// --- Static Functions --- + +template<typename T> +inline static T abs(const T& value) { + return value < 0 ? - value : value; +} + +template<typename T> +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) { + int32_t mask; + switch (keyCode) { + case KEYCODE_ALT_LEFT: + mask = META_ALT_LEFT_ON; + break; + case KEYCODE_ALT_RIGHT: + mask = META_ALT_RIGHT_ON; + break; + case KEYCODE_SHIFT_LEFT: + mask = META_SHIFT_LEFT_ON; + break; + case KEYCODE_SHIFT_RIGHT: + mask = META_SHIFT_RIGHT_ON; + break; + case KEYCODE_SYM: + mask = META_SYM_ON; + break; + default: + return oldMetaState; + } + + int32_t newMetaState = down ? oldMetaState | mask : oldMetaState & ~ mask + & ~ (META_ALT_ON | META_SHIFT_ON); + + if (newMetaState & (META_ALT_LEFT_ON | META_ALT_RIGHT_ON)) { + newMetaState |= META_ALT_ON; + } + + if (newMetaState & (META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON)) { + newMetaState |= META_SHIFT_ON; + } + + return newMetaState; +} + +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 + { KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT }, + { KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN }, + { KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT }, + { KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP }, +}; +static const int keyCodeRotationMapSize = + sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); + +int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { + if (orientation != InputDispatchPolicyInterface::ROTATION_0) { + for (int i = 0; i < keyCodeRotationMapSize; i++) { + if (keyCode == keyCodeRotationMap[i][0]) { + return keyCodeRotationMap[i][orientation]; + } + } + } + return keyCode; +} + + +// --- InputDevice --- + +InputDevice::InputDevice(int32_t id, uint32_t classes, String8 name) : + id(id), classes(classes), name(name), ignored(false) { +} + +void InputDevice::reset() { + if (isKeyboard()) { + keyboard.reset(); + } + + if (isTrackball()) { + trackball.reset(); + } + + if (isMultiTouchScreen()) { + multiTouchScreen.reset(); + } else if (isSingleTouchScreen()) { + singleTouchScreen.reset(); + } + + if (isTouchScreen()) { + touchScreen.reset(); + } +} + + +// --- InputDevice::TouchData --- + +void InputDevice::TouchData::copyFrom(const TouchData& other) { + pointerCount = other.pointerCount; + idBits = other.idBits; + + for (uint32_t i = 0; i < pointerCount; i++) { + pointers[i] = other.pointers[i]; + idToIndex[i] = other.idToIndex[i]; + } +} + + +// --- InputDevice::KeyboardState --- + +void InputDevice::KeyboardState::reset() { + current.metaState = META_NONE; + current.downTime = 0; +} + + +// --- InputDevice::TrackballState --- + +void InputDevice::TrackballState::reset() { + accumulator.clear(); + current.down = false; + current.downTime = 0; +} + + +// --- InputDevice::TouchScreenState --- + +void InputDevice::TouchScreenState::reset() { + lastTouch.clear(); + downTime = 0; + currentVirtualKey.down = false; + + for (uint32_t i = 0; i < MAX_POINTERS; i++) { + averagingTouchFilter.historyStart[i] = 0; + averagingTouchFilter.historyEnd[i] = 0; + } + + jumpyTouchFilter.jumpyPointsDropped = 0; +} + +void InputDevice::TouchScreenState::calculatePointerIds() { + uint32_t currentPointerCount = currentTouch.pointerCount; + uint32_t lastPointerCount = lastTouch.pointerCount; + + if (currentPointerCount == 0) { + // No pointers to assign. + currentTouch.idBits.clear(); + } else if (lastPointerCount == 0) { + // All pointers are new. + currentTouch.idBits.clear(); + for (uint32_t i = 0; i < currentPointerCount; i++) { + currentTouch.pointers[i].id = i; + currentTouch.idToIndex[i] = i; + currentTouch.idBits.markBit(i); + } + } else if (currentPointerCount == 1 && lastPointerCount == 1) { + // Only one pointer and no change in count so it must have the same id as before. + uint32_t id = lastTouch.pointers[0].id; + currentTouch.pointers[0].id = id; + currentTouch.idToIndex[id] = 0; + currentTouch.idBits.value = BitSet32::valueForBit(id); + } else { + // General case. + // We build a heap of squared euclidean distances between current and last pointers + // associated with the current and last pointer indices. Then, we find the best + // match (by distance) for each current pointer. + struct { + uint32_t currentPointerIndex : 8; + uint32_t lastPointerIndex : 8; + uint64_t distance : 48; // squared distance + } heap[MAX_POINTERS * MAX_POINTERS]; + + uint32_t heapSize = 0; + for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; + currentPointerIndex++) { + for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount; + lastPointerIndex++) { + int64_t deltaX = currentTouch.pointers[currentPointerIndex].x + - lastTouch.pointers[lastPointerIndex].x; + int64_t deltaY = currentTouch.pointers[currentPointerIndex].y + - lastTouch.pointers[lastPointerIndex].y; + + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + + // Insert new element into the heap (sift up). + heapSize += 1; + uint32_t insertionIndex = heapSize; + while (insertionIndex > 1) { + uint32_t parentIndex = (insertionIndex - 1) / 2; + if (distance < heap[parentIndex].distance) { + heap[insertionIndex] = heap[parentIndex]; + insertionIndex = parentIndex; + } else { + break; + } + } + heap[insertionIndex].currentPointerIndex = currentPointerIndex; + heap[insertionIndex].lastPointerIndex = lastPointerIndex; + heap[insertionIndex].distance = distance; + } + } + + // Pull matches out by increasing order of distance. + // To avoid reassigning pointers that have already been matched, the loop keeps track + // of which last and current pointers have been matched using the matchedXXXBits variables. + // It also tracks the used pointer id bits. + BitSet32 matchedLastBits(0); + BitSet32 matchedCurrentBits(0); + BitSet32 usedIdBits(0); + bool first = true; + for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) { + for (;;) { + if (first) { + // The first time through the loop, we just consume the root element of + // the heap (the one with smalled distance). + first = false; + } else { + // Previous iterations consumed the root element of the heap. + // Pop root element off of the heap (sift down). + heapSize -= 1; + assert(heapSize > 0); + + // Sift down to find where the element at index heapSize needs to be moved. + uint32_t rootIndex = 0; + for (;;) { + uint32_t childIndex = rootIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[heapSize].distance < heap[childIndex].distance) { + break; + } + + heap[rootIndex] = heap[childIndex]; + rootIndex = childIndex; + } + heap[rootIndex] = heap[heapSize]; + } + + 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 = lastTouch.pointers[lastPointerIndex].id; + currentTouch.pointers[currentPointerIndex].id = id; + currentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + break; + } + } + + // Assign fresh ids to new pointers. + if (currentPointerCount > lastPointerCount) { + for (uint32_t i = currentPointerCount - lastPointerCount; ;) { + uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit(); + uint32_t id = usedIdBits.firstUnmarkedBit(); + + currentTouch.pointers[currentPointerIndex].id = id; + currentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + + if (--i == 0) break; // done + matchedCurrentBits.markBit(currentPointerIndex); + } + } + + // Fix id bits. + currentTouch.idBits = usedIdBits; + } +} + +/* Special hack for devices that have bad screen data: if one of the + * points has moved more than a screen height from the last position, + * then drop it. */ +bool InputDevice::TouchScreenState::applyBadTouchFilter() { + uint32_t pointerCount = currentTouch.pointerCount; + + // Nothing to do if there are no points. + if (pointerCount == 0) { + return false; + } + + // Don't do anything if a finger is going down or up. We run + // here before assigning pointer IDs, so there isn't a good + // way to do per-finger matching. + if (pointerCount != lastTouch.pointerCount) { + return false; + } + + // We consider a single movement across more than a 7/16 of + // the long size of the screen to be bad. This was a magic value + // determined by looking at the maximum distance it is feasible + // to actually move in one sample. + int32_t maxDeltaY = parameters.yAxis.range * 7 / 16; + + // XXX The original code in InputDevice.java included commented out + // code for testing the X axis. Note that when we drop a point + // we don't actually restore the old X either. Strange. + // The old code also tries to track when bad points were previously + // detected but it turns out that due to the placement of a "break" + // at the end of the loop, we never set mDroppedBadPoint to true + // so it is effectively dead code. + // Need to figure out if the old code is busted or just overcomplicated + // but working as intended. + + // Look through all new points and see if any are farther than + // acceptable from all previous points. + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t y = currentTouch.pointers[i].y; + int32_t closestY = INT_MAX; + int32_t closestDeltaY = 0; + +#if DEBUG_HACKS + LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y); +#endif + + for (uint32_t j = pointerCount; j-- > 0; ) { + int32_t lastY = lastTouch.pointers[j].y; + int32_t deltaY = abs(y - lastY); + +#if DEBUG_HACKS + LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d", + j, lastY, deltaY); +#endif + + if (deltaY < maxDeltaY) { + goto SkipSufficientlyClosePoint; + } + if (deltaY < closestDeltaY) { + closestDeltaY = deltaY; + closestY = lastY; + } + } + + // Must not have found a close enough match. +#if DEBUG_HACKS + LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d", + i, y, closestY, closestDeltaY, maxDeltaY); +#endif + + currentTouch.pointers[i].y = closestY; + return true; // XXX original code only corrects one point + + SkipSufficientlyClosePoint: ; + } + + // No change. + return false; +} + +/* Special hack for devices that have bad screen data: drop points where + * the coordinate value for one axis has jumped to the other pointer's location. + */ +bool InputDevice::TouchScreenState::applyJumpyTouchFilter() { + uint32_t pointerCount = currentTouch.pointerCount; + if (lastTouch.pointerCount != pointerCount) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Different pointer count %d -> %d", + lastTouch.pointerCount, pointerCount); + for (uint32_t i = 0; i < pointerCount; i++) { + LOGD(" Pointer %d (%d, %d)", i, + currentTouch.pointers[i].x, currentTouch.pointers[i].y); + } +#endif + + if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) { + if (lastTouch.pointerCount == 1 && pointerCount == 2) { + // Just drop the first few events going from 1 to 2 pointers. + // They're bad often enough that they're not worth considering. + currentTouch.pointerCount = 1; + jumpyTouchFilter.jumpyPointsDropped += 1; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Pointer 2 dropped"); +#endif + return true; + } else if (lastTouch.pointerCount == 2 && pointerCount == 1) { + // The event when we go from 2 -> 1 tends to be messed up too + currentTouch.pointerCount = 2; + currentTouch.pointers[0] = lastTouch.pointers[0]; + currentTouch.pointers[1] = lastTouch.pointers[1]; + jumpyTouchFilter.jumpyPointsDropped += 1; + +#if DEBUG_HACKS + for (int32_t i = 0; i < 2; i++) { + LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i, + currentTouch.pointers[i].x, currentTouch.pointers[i].y); + } +#endif + return true; + } + } + // Reset jumpy points dropped on other transitions or if limit exceeded. + jumpyTouchFilter.jumpyPointsDropped = 0; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Transition - drop limit reset"); +#endif + return false; + } + + // We have the same number of pointers as last time. + // A 'jumpy' point is one where the coordinate value for one axis + // has jumped to the other pointer's location. No need to do anything + // else if we only have one pointer. + if (pointerCount < 2) { + return false; + } + + if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) { + int jumpyEpsilon = parameters.yAxis.range / JUMPY_EPSILON_DIVISOR; + + // We only replace the single worst jumpy point as characterized by pointer distance + // in a single axis. + int32_t badPointerIndex = -1; + int32_t badPointerReplacementIndex = -1; + int32_t badPointerDistance = INT_MIN; // distance to be corrected + + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t x = currentTouch.pointers[i].x; + int32_t y = currentTouch.pointers[i].y; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y); +#endif + + // Check if a touch point is too close to another's coordinates + bool dropX = false, dropY = false; + for (uint32_t j = 0; j < pointerCount; j++) { + if (i == j) { + continue; + } + + if (abs(x - currentTouch.pointers[j].x) <= jumpyEpsilon) { + dropX = true; + break; + } + + if (abs(y - currentTouch.pointers[j].y) <= jumpyEpsilon) { + dropY = true; + break; + } + } + if (! dropX && ! dropY) { + continue; // not jumpy + } + + // Find a replacement candidate by comparing with older points on the + // complementary (non-jumpy) axis. + int32_t distance = INT_MIN; // distance to be corrected + int32_t replacementIndex = -1; + + if (dropX) { + // X looks too close. Find an older replacement point with a close Y. + int32_t smallestDeltaY = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaY = abs(y - lastTouch.pointers[j].y); + if (deltaY < smallestDeltaY) { + smallestDeltaY = deltaY; + replacementIndex = j; + } + } + distance = abs(x - lastTouch.pointers[replacementIndex].x); + } else { + // Y looks too close. Find an older replacement point with a close X. + int32_t smallestDeltaX = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaX = abs(x - lastTouch.pointers[j].x); + if (deltaX < smallestDeltaX) { + smallestDeltaX = deltaX; + replacementIndex = j; + } + } + distance = abs(y - lastTouch.pointers[replacementIndex].y); + } + + // If replacing this pointer would correct a worse error than the previous ones + // considered, then use this replacement instead. + if (distance > badPointerDistance) { + badPointerIndex = i; + badPointerReplacementIndex = replacementIndex; + badPointerDistance = distance; + } + } + + // Correct the jumpy pointer if one was found. + if (badPointerIndex >= 0) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)", + badPointerIndex, + lastTouch.pointers[badPointerReplacementIndex].x, + lastTouch.pointers[badPointerReplacementIndex].y); +#endif + + currentTouch.pointers[badPointerIndex].x = + lastTouch.pointers[badPointerReplacementIndex].x; + currentTouch.pointers[badPointerIndex].y = + lastTouch.pointers[badPointerReplacementIndex].y; + jumpyTouchFilter.jumpyPointsDropped += 1; + return true; + } + } + + jumpyTouchFilter.jumpyPointsDropped = 0; + return false; +} + +/* Special hack for devices that have bad screen data: aggregate and + * compute averages of the coordinate data, to reduce the amount of + * jitter seen by applications. */ +void InputDevice::TouchScreenState::applyAveragingTouchFilter() { + for (uint32_t currentIndex = 0; currentIndex < currentTouch.pointerCount; currentIndex++) { + uint32_t id = currentTouch.pointers[currentIndex].id; + int32_t x = currentTouch.pointers[currentIndex].x; + int32_t y = currentTouch.pointers[currentIndex].y; + int32_t pressure = currentTouch.pointers[currentIndex].pressure; + + if (lastTouch.idBits.hasBit(id)) { + // Pointer still down compute average. + uint32_t start = averagingTouchFilter.historyStart[id]; + uint32_t end = averagingTouchFilter.historyEnd[id]; + + int64_t deltaX = x - averagingTouchFilter.historyData[end].pointers[id].x; + int64_t deltaY = y - averagingTouchFilter.historyData[end].pointers[id].y; + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld", + id, distance); +#endif + + if (distance < AVERAGING_DISTANCE_LIMIT) { + end += 1; + if (end > AVERAGING_HISTORY_SIZE) { + end = 0; + } + + if (end == start) { + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } + + averagingTouchFilter.historyStart[id] = start; + averagingTouchFilter.historyEnd[id] = end; + averagingTouchFilter.historyData[end].pointers[id].x = x; + averagingTouchFilter.historyData[end].pointers[id].y = y; + averagingTouchFilter.historyData[end].pointers[id].pressure = pressure; + + int32_t averagedX = 0; + int32_t averagedY = 0; + int32_t totalPressure = 0; + for (;;) { + int32_t historicalX = averagingTouchFilter.historyData[start].pointers[id].x; + int32_t historicalY = averagingTouchFilter.historyData[start].pointers[id].x; + int32_t historicalPressure = averagingTouchFilter.historyData[start] + .pointers[id].pressure; + + averagedX += historicalX; + averagedY += historicalY; + totalPressure += historicalPressure; + + if (start == end) { + break; + } + + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } + + averagedX /= totalPressure; + averagedY /= totalPressure; + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - " + "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure, + averagedX, averagedY); +#endif + + currentTouch.pointers[currentIndex].x = averagedX; + currentTouch.pointers[currentIndex].y = averagedY; + } else { +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id); +#endif + } + } else { +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id); +#endif + } + + // Reset pointer history. + averagingTouchFilter.historyStart[id] = 0; + averagingTouchFilter.historyEnd[id] = 0; + averagingTouchFilter.historyData[0].pointers[id].x = x; + averagingTouchFilter.historyData[0].pointers[id].y = y; + averagingTouchFilter.historyData[0].pointers[id].pressure = pressure; + } +} + +bool InputDevice::TouchScreenState::isPointInsideDisplay(int32_t x, int32_t y) const { + return x >= parameters.xAxis.minValue + && x <= parameters.xAxis.maxValue + && y >= parameters.yAxis.minValue + && y <= parameters.yAxis.maxValue; +} + + +// --- InputDevice::SingleTouchScreenState --- + +void InputDevice::SingleTouchScreenState::reset() { + accumulator.clear(); + current.down = false; + current.x = 0; + current.y = 0; + current.pressure = 0; + current.size = 0; +} + + +// --- InputDevice::MultiTouchScreenState --- + +void InputDevice::MultiTouchScreenState::reset() { + accumulator.clear(); +} + + +// --- InputReader --- + +InputReader::InputReader(const sp<EventHubInterface>& eventHub, + const sp<InputDispatchPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher) : + mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher) { + resetGlobalMetaState(); + resetDisplayProperties(); + updateGlobalVirtualKeyState(); +} + +InputReader::~InputReader() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } +} + +void InputReader::loopOnce() { + RawEvent rawEvent; + mEventHub->getEvent(& rawEvent.deviceId, & rawEvent.type, & rawEvent.scanCode, + & rawEvent.keyCode, & rawEvent.flags, & rawEvent.value, & rawEvent.when); + + // Replace the event timestamp so it is in same timebase as java.lang.System.nanoTime() + // and android.os.SystemClock.uptimeMillis() as expected by the rest of the system. + rawEvent.when = systemTime(SYSTEM_TIME_MONOTONIC); + +#if DEBUG_RAW_EVENTS + LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d", + rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode, + rawEvent.value); +#endif + + process(& rawEvent); +} + +void InputReader::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EventHubInterface::DEVICE_ADDED: + handleDeviceAdded(rawEvent); + break; + + case EventHubInterface::DEVICE_REMOVED: + handleDeviceRemoved(rawEvent); + break; + + case EV_SYN: + handleSync(rawEvent); + break; + + case EV_KEY: + handleKey(rawEvent); + break; + + case EV_REL: + handleRelativeMotion(rawEvent); + break; + + case EV_ABS: + handleAbsoluteMotion(rawEvent); + break; + + case EV_SW: + handleSwitch(rawEvent); + break; + } +} + +void InputReader::handleDeviceAdded(const RawEvent* rawEvent) { + InputDevice* device = getDevice(rawEvent->deviceId); + if (device) { + LOGW("Ignoring spurious device added event for deviceId %d.", rawEvent->deviceId); + return; + } + + addDevice(rawEvent->when, rawEvent->deviceId); +} + +void InputReader::handleDeviceRemoved(const RawEvent* rawEvent) { + InputDevice* device = getDevice(rawEvent->deviceId); + if (! device) { + LOGW("Ignoring spurious device removed event for deviceId %d.", rawEvent->deviceId); + return; + } + + removeDevice(rawEvent->when, device); +} + +void InputReader::handleSync(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + if (rawEvent->scanCode == SYN_MT_REPORT) { + // MultiTouch Sync: The driver has returned all data for *one* of the pointers. + // We drop pointers with pressure <= 0 since that indicates they are not down. + if (device->isMultiTouchScreen()) { + uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount; + + if (device->multiTouchScreen.accumulator.pointers[pointerIndex].fields) { + if (pointerIndex == MAX_POINTERS) { + LOGW("MultiTouch device driver returned more than maximum of %d pointers.", + MAX_POINTERS); + } else { + pointerIndex += 1; + device->multiTouchScreen.accumulator.pointerCount = pointerIndex; + } + } + + device->multiTouchScreen.accumulator.pointers[pointerIndex].clear(); + } + } else if (rawEvent->scanCode == SYN_REPORT) { + // General Sync: The driver has returned all data for the current event update. + if (device->isMultiTouchScreen()) { + if (device->multiTouchScreen.accumulator.isDirty()) { + onMultiTouchScreenStateChanged(rawEvent->when, device); + device->multiTouchScreen.accumulator.clear(); + } + } else if (device->isSingleTouchScreen()) { + if (device->singleTouchScreen.accumulator.isDirty()) { + onSingleTouchScreenStateChanged(rawEvent->when, device); + device->singleTouchScreen.accumulator.clear(); + } + } + + if (device->trackball.accumulator.isDirty()) { + onTrackballStateChanged(rawEvent->when, device); + device->trackball.accumulator.clear(); + } + } +} + +void InputReader::handleKey(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + bool down = rawEvent->value != 0; + int32_t scanCode = rawEvent->scanCode; + + if (device->isKeyboard() && (scanCode < BTN_FIRST || scanCode > BTN_LAST)) { + int32_t keyCode = rawEvent->keyCode; + onKey(rawEvent->when, device, down, keyCode, scanCode, rawEvent->flags); + } else if (device->isSingleTouchScreen()) { + switch (rawEvent->scanCode) { + case BTN_TOUCH: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH; + device->singleTouchScreen.accumulator.btnTouch = down; + break; + } + } else if (device->isTrackball()) { + switch (rawEvent->scanCode) { + case BTN_MOUSE: + device->trackball.accumulator.fields |= + InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE; + device->trackball.accumulator.btnMouse = down; + + // send the down immediately + // XXX this emulates the old behavior of KeyInputQueue, unclear whether it is + // necessary or if we can wait until the next sync + onTrackballStateChanged(rawEvent->when, device); + device->trackball.accumulator.clear(); + break; + } + } +} + +void InputReader::handleRelativeMotion(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + if (device->isTrackball()) { + switch (rawEvent->scanCode) { + case REL_X: + device->trackball.accumulator.fields |= + InputDevice::TrackballState::Accumulator::FIELD_REL_X; + device->trackball.accumulator.relX = rawEvent->value; + break; + case REL_Y: + device->trackball.accumulator.fields |= + InputDevice::TrackballState::Accumulator::FIELD_REL_Y; + device->trackball.accumulator.relY = rawEvent->value; + break; + } + } +} + +void InputReader::handleAbsoluteMotion(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + if (device->isMultiTouchScreen()) { + uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount; + InputDevice::MultiTouchScreenState::Accumulator::Pointer* pointer = + & device->multiTouchScreen.accumulator.pointers[pointerIndex]; + + switch (rawEvent->scanCode) { + case ABS_MT_POSITION_X: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X; + pointer->absMTPositionX = rawEvent->value; + break; + case ABS_MT_POSITION_Y: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y; + pointer->absMTPositionY = rawEvent->value; + break; + case ABS_MT_TOUCH_MAJOR: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR; + pointer->absMTTouchMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MAJOR: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; + pointer->absMTWidthMajor = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TRACKING_ID; + pointer->absMTTrackingId = rawEvent->value; + break; + } + } else if (device->isSingleTouchScreen()) { + switch (rawEvent->scanCode) { + case ABS_X: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X; + device->singleTouchScreen.accumulator.absX = rawEvent->value; + break; + case ABS_Y: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y; + device->singleTouchScreen.accumulator.absY = rawEvent->value; + break; + case ABS_PRESSURE: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE; + device->singleTouchScreen.accumulator.absPressure = rawEvent->value; + break; + case ABS_TOOL_WIDTH: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH; + device->singleTouchScreen.accumulator.absToolWidth = rawEvent->value; + break; + } + } +} + +void InputReader::handleSwitch(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + onSwitch(rawEvent->when, device, rawEvent->value != 0, rawEvent->scanCode); +} + +void InputReader::onKey(nsecs_t when, InputDevice* device, + bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) { + /* Refresh display properties so we can rotate key codes according to display orientation */ + + if (! refreshDisplayProperties()) { + return; + } + + /* Update device state */ + + int32_t oldMetaState = device->keyboard.current.metaState; + int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState); + if (oldMetaState != newMetaState) { + device->keyboard.current.metaState = newMetaState; + resetGlobalMetaState(); + } + + // FIXME if we send a down event about a rotated key press we should ensure that we send + // a corresponding up event about the rotated key press even if the orientation + // has changed in the meantime + keyCode = rotateKeyCode(keyCode, mDisplayOrientation); + + if (down) { + device->keyboard.current.downTime = when; + } + + /* Apply policy */ + + int32_t policyActions = mPolicy->interceptKey(when, device->id, + down, keyCode, scanCode, policyFlags); + + if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + return; // event dropped + } + + /* Enqueue key event for dispatch */ + + int32_t keyEventAction; + if (down) { + device->keyboard.current.downTime = when; + keyEventAction = KEY_EVENT_ACTION_DOWN; + } else { + keyEventAction = KEY_EVENT_ACTION_UP; + } + + int32_t keyEventFlags = KEY_EVENT_FLAG_FROM_SYSTEM; + if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) { + keyEventFlags = keyEventFlags | KEY_EVENT_FLAG_WOKE_HERE; + } + + mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags, + keyEventAction, keyEventFlags, keyCode, scanCode, + device->keyboard.current.metaState, + device->keyboard.current.downTime); +} + +void InputReader::onSwitch(nsecs_t when, InputDevice* device, bool down, + int32_t code) { + switch (code) { + case SW_LID: + mDispatcher->notifyLidSwitchChanged(when, ! down); + } +} + +void InputReader::onMultiTouchScreenStateChanged(nsecs_t when, + InputDevice* device) { + static const uint32_t REQUIRED_FIELDS = + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X + | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y + | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR + | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; + + /* Refresh display properties so we can map touch screen coords into display coords */ + + if (! refreshDisplayProperties()) { + return; + } + + /* Update device state */ + + InputDevice::MultiTouchScreenState* in = & device->multiTouchScreen; + InputDevice::TouchData* out = & device->touchScreen.currentTouch; + + uint32_t inCount = in->accumulator.pointerCount; + uint32_t outCount = 0; + bool havePointerIds = true; + + out->clear(); + + for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) { + uint32_t fields = in->accumulator.pointers[inIndex].fields; + + if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) { +#if DEBUG_POINTERS + LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d", + inIndex, fields); + continue; +#endif + } + + if (in->accumulator.pointers[inIndex].absMTTouchMajor <= 0) { + // Pointer is not down. Drop it. + continue; + } + + // FIXME assignment of pressure may be incorrect, probably better to let + // pressure = touch / width. Later on we pass width to MotionEvent as a size, which + // isn't quite right either. Should be using touch for that. + out->pointers[outCount].x = in->accumulator.pointers[inIndex].absMTPositionX; + out->pointers[outCount].y = in->accumulator.pointers[inIndex].absMTPositionY; + out->pointers[outCount].pressure = in->accumulator.pointers[inIndex].absMTTouchMajor; + out->pointers[outCount].size = in->accumulator.pointers[inIndex].absMTWidthMajor; + + if (havePointerIds) { + if (fields & InputDevice::MultiTouchScreenState::Accumulator:: + FIELD_ABS_MT_TRACKING_ID) { + uint32_t id = uint32_t(in->accumulator.pointers[inIndex].absMTTrackingId); + + if (id > MAX_POINTER_ID) { +#if DEBUG_POINTERS + LOGD("Pointers: Ignoring driver provided pointer id %d because " + "it is larger than max supported id %d for optimizations", + id, MAX_POINTER_ID); +#endif + havePointerIds = false; + } + else { + out->pointers[outCount].id = id; + out->idToIndex[id] = outCount; + out->idBits.markBit(id); + } + } else { + havePointerIds = false; + } + } + + outCount += 1; + } + + out->pointerCount = outCount; + + onTouchScreenChanged(when, device, havePointerIds); +} + +void InputReader::onSingleTouchScreenStateChanged(nsecs_t when, + InputDevice* device) { + static const uint32_t POSITION_FIELDS = + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X + | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y + | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE + | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH; + + /* Refresh display properties so we can map touch screen coords into display coords */ + + if (! refreshDisplayProperties()) { + return; + } + + /* Update device state */ + + InputDevice::SingleTouchScreenState* in = & device->singleTouchScreen; + InputDevice::TouchData* out = & device->touchScreen.currentTouch; + + uint32_t fields = in->accumulator.fields; + + if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH) { + in->current.down = in->accumulator.btnTouch; + } + + if ((fields & POSITION_FIELDS) == POSITION_FIELDS) { + in->current.x = in->accumulator.absX; + in->current.y = in->accumulator.absY; + in->current.pressure = in->accumulator.absPressure; + in->current.size = in->accumulator.absToolWidth; + } + + out->clear(); + + if (in->current.down) { + out->pointerCount = 1; + out->pointers[0].id = 0; + out->pointers[0].x = in->current.x; + out->pointers[0].y = in->current.y; + out->pointers[0].pressure = in->current.pressure; + out->pointers[0].size = in->current.size; + out->idToIndex[0] = 0; + out->idBits.markBit(0); + } + + onTouchScreenChanged(when, device, true); +} + +void InputReader::onTouchScreenChanged(nsecs_t when, + InputDevice* device, bool havePointerIds) { + /* Apply policy */ + + int32_t policyActions = mPolicy->interceptTouch(when); + + uint32_t policyFlags = 0; + if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + device->touchScreen.lastTouch.clear(); + return; // event dropped + } + + /* Preprocess pointer data */ + + if (device->touchScreen.parameters.useBadTouchFilter) { + if (device->touchScreen.applyBadTouchFilter()) { + havePointerIds = false; + } + } + + if (device->touchScreen.parameters.useJumpyTouchFilter) { + if (device->touchScreen.applyJumpyTouchFilter()) { + havePointerIds = false; + } + } + + if (! havePointerIds) { + device->touchScreen.calculatePointerIds(); + } + + InputDevice::TouchData temp; + InputDevice::TouchData* savedTouch; + if (device->touchScreen.parameters.useAveragingTouchFilter) { + temp.copyFrom(device->touchScreen.currentTouch); + savedTouch = & temp; + + device->touchScreen.applyAveragingTouchFilter(); + } else { + savedTouch = & device->touchScreen.currentTouch; + } + + /* Process virtual keys or touches */ + + if (! consumeVirtualKeyTouches(when, device, policyFlags)) { + dispatchTouches(when, device, policyFlags); + } + + // Copy current touch to last touch in preparation for the next cycle. + device->touchScreen.lastTouch.copyFrom(*savedTouch); +} + +bool InputReader::consumeVirtualKeyTouches(nsecs_t when, + InputDevice* device, uint32_t policyFlags) { + if (device->touchScreen.currentVirtualKey.down) { + if (device->touchScreen.currentTouch.pointerCount == 0) { + // Pointer went up while virtual key was down. Send key up event. + device->touchScreen.currentVirtualKey.down = false; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", + device->touchScreen.currentVirtualKey.keyCode, + device->touchScreen.currentVirtualKey.scanCode); +#endif + + dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP, + KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + return true; // consumed + } + + int32_t x = device->touchScreen.currentTouch.pointers[0].x; + int32_t y = device->touchScreen.currentTouch.pointers[0].y; + if (device->touchScreen.isPointInsideDisplay(x, y)) { + // Pointer moved inside the display area. Send key cancellation. + device->touchScreen.currentVirtualKey.down = false; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", + device->touchScreen.currentVirtualKey.keyCode, + device->touchScreen.currentVirtualKey.scanCode); +#endif + + dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP, + KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY + | KEY_EVENT_FLAG_CANCELED); + + // Clear the last touch data so we will consider the pointer as having just been + // pressed down when generating subsequent motion events. + device->touchScreen.lastTouch.clear(); + return false; // not consumed + } + } else if (device->touchScreen.currentTouch.pointerCount > 0 + && device->touchScreen.lastTouch.pointerCount == 0) { + int32_t x = device->touchScreen.currentTouch.pointers[0].x; + int32_t y = device->touchScreen.currentTouch.pointers[0].y; + for (size_t i = 0; i < device->touchScreen.virtualKeys.size(); i++) { + const InputDevice::VirtualKey& virtualKey = device->touchScreen.virtualKeys[i]; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " + "left=%d, top=%d, right=%d, bottom=%d", + x, y, + virtualKey.keyCode, virtualKey.scanCode, + virtualKey.hitLeft, virtualKey.hitTop, + virtualKey.hitRight, virtualKey.hitBottom); +#endif + + if (virtualKey.isHit(x, y)) { + device->touchScreen.currentVirtualKey.down = true; + device->touchScreen.currentVirtualKey.downTime = when; + device->touchScreen.currentVirtualKey.keyCode = virtualKey.keyCode; + device->touchScreen.currentVirtualKey.scanCode = virtualKey.scanCode; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", + device->touchScreen.currentVirtualKey.keyCode, + device->touchScreen.currentVirtualKey.scanCode); +#endif + + dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_DOWN, + KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + return true; // consumed + } + } + } + + return false; // not consumed +} + +void InputReader::dispatchVirtualKey(nsecs_t when, + InputDevice* device, uint32_t policyFlags, + int32_t keyEventAction, int32_t keyEventFlags) { + int32_t keyCode = device->touchScreen.currentVirtualKey.keyCode; + int32_t scanCode = device->touchScreen.currentVirtualKey.scanCode; + nsecs_t downTime = device->touchScreen.currentVirtualKey.downTime; + int32_t metaState = globalMetaState(); + + updateGlobalVirtualKeyState(); + + mPolicy->virtualKeyFeedback(when, device->id, keyEventAction, keyEventFlags, + keyCode, scanCode, metaState, downTime); + + mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags, + keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime); +} + +void InputReader::dispatchTouches(nsecs_t when, + InputDevice* device, uint32_t policyFlags) { + uint32_t currentPointerCount = device->touchScreen.currentTouch.pointerCount; + uint32_t lastPointerCount = device->touchScreen.lastTouch.pointerCount; + if (currentPointerCount == 0 && lastPointerCount == 0) { + return; // nothing to do! + } + + BitSet32 currentIdBits = device->touchScreen.currentTouch.idBits; + BitSet32 lastIdBits = device->touchScreen.lastTouch.idBits; + + if (currentIdBits == lastIdBits) { + // No pointer id changes so this is a move event. + // The dispatcher takes care of batching moves so we don't have to deal with that here. + int32_t motionEventAction = MOTION_EVENT_ACTION_MOVE; + dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch, + currentIdBits, motionEventAction); + } else { + // There may be pointers going up and pointers going down at the same time when pointer + // ids are reported by the device driver. + BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value); + BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value); + BitSet32 activeIdBits(lastIdBits.value); + + while (! upIdBits.isEmpty()) { + uint32_t upId = upIdBits.firstMarkedBit(); + upIdBits.clearBit(upId); + BitSet32 oldActiveIdBits = activeIdBits; + activeIdBits.clearBit(upId); + + int32_t motionEventAction; + if (activeIdBits.isEmpty()) { + motionEventAction = MOTION_EVENT_ACTION_UP; + } else { + motionEventAction = MOTION_EVENT_ACTION_POINTER_UP + | (upId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + + dispatchTouch(when, device, policyFlags, & device->touchScreen.lastTouch, + oldActiveIdBits, motionEventAction); + } + + while (! downIdBits.isEmpty()) { + uint32_t downId = downIdBits.firstMarkedBit(); + downIdBits.clearBit(downId); + BitSet32 oldActiveIdBits = activeIdBits; + activeIdBits.markBit(downId); + + int32_t motionEventAction; + if (oldActiveIdBits.isEmpty()) { + motionEventAction = MOTION_EVENT_ACTION_DOWN; + device->touchScreen.downTime = when; + } else { + motionEventAction = MOTION_EVENT_ACTION_POINTER_DOWN + | (downId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + + dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch, + activeIdBits, motionEventAction); + } + } +} + +void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags, + InputDevice::TouchData* touch, BitSet32 idBits, + int32_t motionEventAction) { + int32_t orientedWidth, orientedHeight; + switch (mDisplayOrientation) { + case InputDispatchPolicyInterface::ROTATION_90: + case InputDispatchPolicyInterface::ROTATION_270: + orientedWidth = mDisplayHeight; + orientedHeight = mDisplayWidth; + break; + default: + orientedWidth = mDisplayWidth; + orientedHeight = mDisplayHeight; + break; + } + + uint32_t pointerCount = 0; + int32_t pointerIds[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + + // Walk through the the active pointers and map touch screen coordinates (TouchData) into + // display coordinates (PointerCoords) and adjust for display orientation. + while (! idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = touch->idToIndex[id]; + + float x = (float(touch->pointers[index].x) + - device->touchScreen.parameters.xAxis.minValue) + * device->touchScreen.precalculated.xScale; + float y = (float(touch->pointers[index].y) + - device->touchScreen.parameters.yAxis.minValue) + * device->touchScreen.precalculated.yScale; + float pressure = (float(touch->pointers[index].pressure) + - device->touchScreen.parameters.pressureAxis.minValue) + * device->touchScreen.precalculated.pressureScale; + float size = (float(touch->pointers[index].size) + - device->touchScreen.parameters.sizeAxis.minValue) + * device->touchScreen.precalculated.sizeScale; + + switch (mDisplayOrientation) { + case InputDispatchPolicyInterface::ROTATION_90: { + float xTemp = x; + x = y; + y = mDisplayHeight - xTemp; + break; + } + case InputDispatchPolicyInterface::ROTATION_180: { + x = mDisplayWidth - x; + y = mDisplayHeight - y; + break; + } + case InputDispatchPolicyInterface::ROTATION_270: { + float xTemp = x; + x = mDisplayWidth - y; + y = xTemp; + break; + } + } + + pointerIds[pointerCount] = int32_t(id); + + pointerCoords[pointerCount].x = x; + pointerCoords[pointerCount].y = y; + pointerCoords[pointerCount].pressure = pressure; + pointerCoords[pointerCount].size = size; + + pointerCount += 1; + } + + // Check edge flags by looking only at the first pointer since the flags are + // global to the event. + // XXX Maybe we should revise the edge flags API to work on a per-pointer basis. + int32_t motionEventEdgeFlags = 0; + if (motionEventAction == MOTION_EVENT_ACTION_DOWN) { + if (pointerCoords[0].x <= 0) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_LEFT; + } else if (pointerCoords[0].x >= orientedWidth) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_RIGHT; + } + if (pointerCoords[0].y <= 0) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_TOP; + } else if (pointerCoords[0].y >= orientedHeight) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_BOTTOM; + } + } + + nsecs_t downTime = device->touchScreen.downTime; + mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TOUCH, policyFlags, + motionEventAction, globalMetaState(), motionEventEdgeFlags, + pointerCount, pointerIds, pointerCoords, + 0, 0, downTime); +} + +void InputReader::onTrackballStateChanged(nsecs_t when, + InputDevice* device) { + static const uint32_t DELTA_FIELDS = + InputDevice::TrackballState::Accumulator::FIELD_REL_X + | InputDevice::TrackballState::Accumulator::FIELD_REL_Y; + + /* Refresh display properties so we can trackball moves according to display orientation */ + + if (! refreshDisplayProperties()) { + return; + } + + /* Update device state */ + + uint32_t fields = device->trackball.accumulator.fields; + bool downChanged = fields & InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE; + bool deltaChanged = (fields & DELTA_FIELDS) == DELTA_FIELDS; + + bool down; + if (downChanged) { + if (device->trackball.accumulator.btnMouse) { + device->trackball.current.down = true; + device->trackball.current.downTime = when; + down = true; + } else { + device->trackball.current.down = false; + down = false; + } + } else { + down = device->trackball.current.down; + } + + /* Apply policy */ + + int32_t policyActions = mPolicy->interceptTrackball(when, downChanged, down, deltaChanged); + + uint32_t policyFlags = 0; + if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + return; // event dropped + } + + /* Enqueue motion event for dispatch */ + + int32_t motionEventAction; + if (downChanged) { + motionEventAction = down ? MOTION_EVENT_ACTION_DOWN : MOTION_EVENT_ACTION_UP; + } else { + motionEventAction = MOTION_EVENT_ACTION_MOVE; + } + + int32_t pointerId = 0; + PointerCoords pointerCoords; + pointerCoords.x = device->trackball.accumulator.relX + * device->trackball.precalculated.xScale; + pointerCoords.y = device->trackball.accumulator.relY + * device->trackball.precalculated.yScale; + pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise. + pointerCoords.size = 0; + + float temp; + switch (mDisplayOrientation) { + case InputDispatchPolicyInterface::ROTATION_90: + temp = pointerCoords.x; + pointerCoords.x = pointerCoords.y; + pointerCoords.y = - temp; + break; + + case InputDispatchPolicyInterface::ROTATION_180: + pointerCoords.x = - pointerCoords.x; + pointerCoords.y = - pointerCoords.y; + break; + + case InputDispatchPolicyInterface::ROTATION_270: + temp = pointerCoords.x; + pointerCoords.x = - pointerCoords.y; + pointerCoords.y = temp; + break; + } + + mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TRACKBALL, policyFlags, + motionEventAction, globalMetaState(), MOTION_EVENT_EDGE_FLAG_NONE, + 1, & pointerId, & pointerCoords, + device->trackball.precalculated.xPrecision, + device->trackball.precalculated.yPrecision, + device->trackball.current.downTime); +} + +void InputReader::onConfigurationChanged(nsecs_t when) { + // Reset global meta state because it depends on the list of all configured devices. + resetGlobalMetaState(); + + // Reset virtual keys, just in case. + updateGlobalVirtualKeyState(); + + // Enqueue configuration changed. + // XXX This stuff probably needs to be tracked elsewhere in an input device registry + // of some kind that can be asynchronously updated and queried. (Same as above?) + int32_t touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_NOTOUCH; + int32_t keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_NOKEYS; + int32_t navigationConfig = InputDispatchPolicyInterface::NAVIGATION_NONAV; + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + int32_t deviceClasses = device->classes; + + if (deviceClasses & INPUT_DEVICE_CLASS_TOUCHSCREEN) { + touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_FINGER; + } + if (deviceClasses & INPUT_DEVICE_CLASS_ALPHAKEY) { + keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_QWERTY; + } + if (deviceClasses & INPUT_DEVICE_CLASS_TRACKBALL) { + navigationConfig = InputDispatchPolicyInterface::NAVIGATION_TRACKBALL; + } else if (deviceClasses & INPUT_DEVICE_CLASS_DPAD) { + navigationConfig = InputDispatchPolicyInterface::NAVIGATION_DPAD; + } + } + + mDispatcher->notifyConfigurationChanged(when, touchScreenConfig, + keyboardConfig, navigationConfig); +} + +bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when, + int32_t policyActions, uint32_t* policyFlags) { + if (policyActions & InputDispatchPolicyInterface::ACTION_APP_SWITCH_COMING) { + mDispatcher->notifyAppSwitchComing(when); + } + + if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) { + *policyFlags |= POLICY_FLAG_WOKE_HERE; + } + + if (policyActions & InputDispatchPolicyInterface::ACTION_BRIGHT_HERE) { + *policyFlags |= POLICY_FLAG_BRIGHT_HERE; + } + + return policyActions & InputDispatchPolicyInterface::ACTION_DISPATCH; +} + +void InputReader::resetDisplayProperties() { + mDisplayWidth = mDisplayHeight = -1; + mDisplayOrientation = -1; +} + +bool InputReader::refreshDisplayProperties() { + int32_t newWidth, newHeight, newOrientation; + if (mPolicy->getDisplayInfo(0, & newWidth, & newHeight, & newOrientation)) { + if (newWidth != mDisplayWidth || newHeight != mDisplayHeight) { + LOGD("Display size changed from %dx%d to %dx%d, updating device configuration", + mDisplayWidth, mDisplayHeight, newWidth, newHeight); + + mDisplayWidth = newWidth; + mDisplayHeight = newHeight; + + for (size_t i = 0; i < mDevices.size(); i++) { + configureDeviceForCurrentDisplaySize(mDevices.valueAt(i)); + } + } + + mDisplayOrientation = newOrientation; + return true; + } else { + resetDisplayProperties(); + return false; + } +} + +InputDevice* InputReader::getDevice(int32_t deviceId) { + ssize_t index = mDevices.indexOfKey(deviceId); + return index >= 0 ? mDevices.valueAt((size_t) index) : NULL; +} + +InputDevice* InputReader::getNonIgnoredDevice(int32_t deviceId) { + InputDevice* device = getDevice(deviceId); + return device && ! device->ignored ? device : NULL; +} + +void InputReader::addDevice(nsecs_t when, int32_t deviceId) { + uint32_t classes = mEventHub->getDeviceClasses(deviceId); + String8 name = mEventHub->getDeviceName(deviceId); + InputDevice* device = new InputDevice(deviceId, classes, name); + + if (classes != 0) { + LOGI("Device added: id=0x%x, name=%s, classes=%02x", device->id, + device->name.string(), device->classes); + + configureDevice(device); + } else { + LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", device->id, + device->name.string()); + + device->ignored = true; + } + + device->reset(); + + mDevices.add(deviceId, device); + + if (! device->ignored) { + onConfigurationChanged(when); + } +} + +void InputReader::removeDevice(nsecs_t when, InputDevice* device) { + mDevices.removeItem(device->id); + + if (! device->ignored) { + LOGI("Device removed: id=0x%x, name=%s, classes=%02x", device->id, + device->name.string(), device->classes); + + onConfigurationChanged(when); + } else { + LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)", device->id, + device->name.string()); + } + + delete device; +} + +void InputReader::configureDevice(InputDevice* device) { + if (device->isMultiTouchScreen()) { + configureAbsoluteAxisInfo(device, ABS_MT_POSITION_X, "X", + & device->touchScreen.parameters.xAxis); + configureAbsoluteAxisInfo(device, ABS_MT_POSITION_Y, "Y", + & device->touchScreen.parameters.yAxis); + configureAbsoluteAxisInfo(device, ABS_MT_TOUCH_MAJOR, "Pressure", + & device->touchScreen.parameters.pressureAxis); + configureAbsoluteAxisInfo(device, ABS_MT_WIDTH_MAJOR, "Size", + & device->touchScreen.parameters.sizeAxis); + } else if (device->isSingleTouchScreen()) { + configureAbsoluteAxisInfo(device, ABS_X, "X", + & device->touchScreen.parameters.xAxis); + configureAbsoluteAxisInfo(device, ABS_Y, "Y", + & device->touchScreen.parameters.yAxis); + configureAbsoluteAxisInfo(device, ABS_PRESSURE, "Pressure", + & device->touchScreen.parameters.pressureAxis); + configureAbsoluteAxisInfo(device, ABS_TOOL_WIDTH, "Size", + & device->touchScreen.parameters.sizeAxis); + } + + if (device->isTouchScreen()) { + device->touchScreen.parameters.useBadTouchFilter = + mPolicy->filterTouchEvents(); + device->touchScreen.parameters.useAveragingTouchFilter = + mPolicy->filterTouchEvents(); + device->touchScreen.parameters.useJumpyTouchFilter = + mPolicy->filterJumpyTouchEvents(); + + device->touchScreen.precalculated.pressureScale = + 1.0f / device->touchScreen.parameters.pressureAxis.range; + device->touchScreen.precalculated.sizeScale = + 1.0f / device->touchScreen.parameters.sizeAxis.range; + } + + if (device->isTrackball()) { + device->trackball.precalculated.xPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + device->trackball.precalculated.yPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + device->trackball.precalculated.xScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + device->trackball.precalculated.yScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + } + + configureDeviceForCurrentDisplaySize(device); +} + +void InputReader::configureDeviceForCurrentDisplaySize(InputDevice* device) { + if (device->isTouchScreen()) { + if (mDisplayWidth < 0) { + LOGD("Skipping part of touch screen configuration since display size is unknown."); + } else { + LOGI("Device configured: id=0x%x, name=%s (display size was changed)", device->id, + device->name.string()); + configureVirtualKeys(device); + + device->touchScreen.precalculated.xScale = + float(mDisplayWidth) / device->touchScreen.parameters.xAxis.range; + device->touchScreen.precalculated.yScale = + float(mDisplayHeight) / device->touchScreen.parameters.yAxis.range; + } + } +} + +void InputReader::configureVirtualKeys(InputDevice* device) { + device->touchScreen.virtualKeys.clear(); + + Vector<InputDispatchPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions; + mPolicy->getVirtualKeyDefinitions(device->name, virtualKeyDefinitions); + if (virtualKeyDefinitions.size() == 0) { + return; + } + + device->touchScreen.virtualKeys.setCapacity(virtualKeyDefinitions.size()); + + int32_t touchScreenLeft = device->touchScreen.parameters.xAxis.minValue; + int32_t touchScreenTop = device->touchScreen.parameters.yAxis.minValue; + int32_t touchScreenWidth = device->touchScreen.parameters.xAxis.range; + int32_t touchScreenHeight = device->touchScreen.parameters.yAxis.range; + + for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) { + const InputDispatchPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition = + virtualKeyDefinitions[i]; + + device->touchScreen.virtualKeys.add(); + InputDevice::VirtualKey& virtualKey = + device->touchScreen.virtualKeys.editTop(); + + virtualKey.scanCode = virtualKeyDefinition.scanCode; + int32_t keyCode; + uint32_t flags; + if (mEventHub->scancodeToKeycode(device->id, virtualKey.scanCode, + & keyCode, & flags)) { + LOGI(" VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode); + device->touchScreen.virtualKeys.pop(); // drop the key + continue; + } + + virtualKey.keyCode = keyCode; + virtualKey.flags = flags; + + // convert the key definition's display coordinates into touch coordinates for a hit box + int32_t halfWidth = virtualKeyDefinition.width / 2; + int32_t halfHeight = virtualKeyDefinition.height / 2; + + virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) + * touchScreenWidth / mDisplayWidth + touchScreenLeft; + virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth) + * touchScreenWidth / mDisplayWidth + touchScreenLeft; + virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) + * touchScreenHeight / mDisplayHeight + touchScreenTop; + virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) + * touchScreenHeight / mDisplayHeight + touchScreenTop; + + LOGI(" VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d", + virtualKey.scanCode, virtualKey.keyCode, + virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom); + } +} + +void InputReader::configureAbsoluteAxisInfo(InputDevice* device, + int axis, const char* name, InputDevice::AbsoluteAxisInfo* out) { + if (! mEventHub->getAbsoluteInfo(device->id, axis, + & out->minValue, & out->maxValue, & out->flat, &out->fuzz)) { + out->range = out->maxValue - out->minValue; + if (out->range != 0) { + LOGI(" %s: min=%d max=%d flat=%d fuzz=%d", + name, out->minValue, out->maxValue, out->flat, out->fuzz); + return; + } + } + + out->minValue = 0; + out->maxValue = 0; + out->flat = 0; + out->fuzz = 0; + out->range = 0; + LOGI(" %s: unknown axis values, setting to zero", name); +} + +void InputReader::resetGlobalMetaState() { + mGlobalMetaState = -1; +} + +int32_t InputReader::globalMetaState() { + if (mGlobalMetaState == -1) { + mGlobalMetaState = 0; + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + if (device->isKeyboard()) { + mGlobalMetaState |= device->keyboard.current.metaState; + } + } + } + return mGlobalMetaState; +} + +void InputReader::updateGlobalVirtualKeyState() { + int32_t keyCode = -1, scanCode = -1; + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + if (device->isTouchScreen()) { + if (device->touchScreen.currentVirtualKey.down) { + keyCode = device->touchScreen.currentVirtualKey.keyCode; + scanCode = device->touchScreen.currentVirtualKey.scanCode; + } + } + } + + { + AutoMutex _l(mExportedStateLock); + + mGlobalVirtualKeyCode = keyCode; + mGlobalVirtualScanCode = scanCode; + } +} + +bool InputReader::getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const { + AutoMutex _l(mExportedStateLock); + + *outKeyCode = mGlobalVirtualKeyCode; + *outScanCode = mGlobalVirtualScanCode; + return mGlobalVirtualKeyCode != -1; +} + + +// --- InputReaderThread --- + +InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) : + Thread(/*canCallJava*/ true), mReader(reader) { +} + +InputReaderThread::~InputReaderThread() { +} + +bool InputReaderThread::threadLoop() { + mReader->loopOnce(); + return true; +} + +} // namespace android diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp new file mode 100644 index 0000000..a24180f --- /dev/null +++ b/libs/ui/InputTransport.cpp @@ -0,0 +1,684 @@ +// +// Copyright 2010 The Android Open Source Project +// +// Provides a shared memory transport for input events. +// +#define LOG_TAG "InputTransport" + +//#define LOG_NDEBUG 0 + +// Log debug messages about channel signalling (send signal, receive signal) +#define DEBUG_CHANNEL_SIGNALS 1 + +// Log debug messages whenever InputChannel objects are created/destroyed +#define DEBUG_CHANNEL_LIFECYCLE 1 + +// Log debug messages about transport actions (initialize, reset, publish, ...) +#define DEBUG_TRANSPORT_ACTIONS 1 + + +#include <cutils/ashmem.h> +#include <cutils/log.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <ui/InputTransport.h> +#include <unistd.h> + +namespace android { + +// Must be at least sizeof(InputMessage) + sufficient space for pointer data +static const int DEFAULT_MESSAGE_BUFFER_SIZE = 16384; + +// Signal sent by the producer to the consumer to inform it that a new message is +// available to be consumed in the shared memory buffer. +static const char INPUT_SIGNAL_DISPATCH = 'D'; + +// Signal sent by the consumer to the producer to inform it that it has finished +// consuming the most recent message. +static const char INPUT_SIGNAL_FINISHED = 'f'; + + +// --- InputChannel --- + +InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd, + int32_t sendPipeFd) : + mName(name), mAshmemFd(ashmemFd), mReceivePipeFd(receivePipeFd), mSendPipeFd(sendPipeFd) { +#if DEBUG_CHANNEL_LIFECYCLE + LOGD("Input channel constructed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", + mName.string(), ashmemFd, receivePipeFd, sendPipeFd); +#endif + + int result = fcntl(mReceivePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make receive pipe " + "non-blocking. errno=%d", mName.string(), errno); + + result = fcntl(mSendPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make send pipe " + "non-blocking. errno=%d", mName.string(), errno); +} + +InputChannel::~InputChannel() { +#if DEBUG_CHANNEL_LIFECYCLE + LOGD("Input channel destroyed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", + mName.string(), mAshmemFd, mReceivePipeFd, mSendPipeFd); +#endif + + ::close(mAshmemFd); + ::close(mReceivePipeFd); + ::close(mSendPipeFd); +} + +status_t InputChannel::openInputChannelPair(const String8& name, + InputChannel** outServerChannel, InputChannel** outClientChannel) { + status_t result; + + int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE); + if (serverAshmemFd < 0) { + result = -errno; + LOGE("channel '%s' ~ Could not create shared memory region. errno=%d", + name.string(), errno); + } else { + result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE); + if (result < 0) { + LOGE("channel '%s' ~ Error %d trying to set protection of ashmem fd %d.", + name.string(), result, serverAshmemFd); + } else { + // Dup the file descriptor because the server and client input channel objects that + // are returned may have different lifetimes but they share the same shared memory region. + int clientAshmemFd; + clientAshmemFd = dup(serverAshmemFd); + if (clientAshmemFd < 0) { + result = -errno; + LOGE("channel '%s' ~ Could not dup() shared memory region fd. errno=%d", + name.string(), errno); + } else { + int forward[2]; + if (pipe(forward)) { + result = -errno; + LOGE("channel '%s' ~ Could not create forward pipe. errno=%d", + name.string(), errno); + } else { + int reverse[2]; + if (pipe(reverse)) { + result = -errno; + LOGE("channel '%s' ~ Could not create reverse pipe. errno=%d", + name.string(), errno); + } else { + String8 serverChannelName = name; + serverChannelName.append(" (server)"); + *outServerChannel = new InputChannel(serverChannelName, + serverAshmemFd, reverse[0], forward[1]); + + String8 clientChannelName = name; + clientChannelName.append(" (client)"); + *outClientChannel = new InputChannel(clientChannelName, + clientAshmemFd, forward[0], reverse[1]); + return OK; + } + ::close(forward[0]); + ::close(forward[1]); + } + ::close(clientAshmemFd); + } + } + ::close(serverAshmemFd); + } + + *outServerChannel = NULL; + *outClientChannel = NULL; + return result; +} + +status_t InputChannel::sendSignal(char signal) { + ssize_t nWrite = ::write(mSendPipeFd, & signal, 1); + + if (nWrite == 1) { +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ sent signal '%c'", mName.string(), signal); +#endif + return OK; + } + +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ error sending signal '%c', errno=%d", mName.string(), signal, errno); +#endif + return -errno; +} + +status_t InputChannel::receiveSignal(char* outSignal) { + ssize_t nRead = ::read(mReceivePipeFd, outSignal, 1); + if (nRead == 1) { +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ received signal '%c'", mName.string(), *outSignal); +#endif + return OK; + } + + if (errno == EAGAIN) { +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ receive signal failed because no signal available", mName.string()); +#endif + return WOULD_BLOCK; + } + +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ receive signal failed, errno=%d", mName.string(), errno); +#endif + return -errno; +} + + +// --- InputPublisher --- + +InputPublisher::InputPublisher(const sp<InputChannel>& channel) : + mChannel(channel), mSharedMessage(NULL), + mPinned(false), mSemaphoreInitialized(false), mWasDispatched(false), + mMotionEventSampleDataTail(NULL) { +} + +InputPublisher::~InputPublisher() { + reset(); + + if (mSharedMessage) { + munmap(mSharedMessage, mAshmemSize); + } +} + +status_t InputPublisher::initialize() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ initialize", + mChannel->getName().string()); +#endif + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_get_size_region(ashmemFd); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d getting size of ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + mAshmemSize = (size_t) result; + + mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); + if (! mSharedMessage) { + LOGE("channel '%s' publisher ~ mmap failed on ashmem fd %d.", + mChannel->getName().string(), ashmemFd); + return NO_MEMORY; + } + + mPinned = true; + mSharedMessage->consumed = false; + + return reset(); +} + +status_t InputPublisher::reset() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ reset", + mChannel->getName().string()); +#endif + + if (mPinned) { + // Destroy the semaphore since we are about to unpin the memory region that contains it. + int result; + if (mSemaphoreInitialized) { + if (mSharedMessage->consumed) { + result = sem_post(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_post.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + + result = sem_destroy(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_destroy.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSemaphoreInitialized = false; + } + + // Unpin the region since we no longer care about its contents. + int ashmemFd = mChannel->getAshmemFd(); + result = ashmem_unpin_region(ashmemFd, 0, 0); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d unpinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mPinned = false; + } + + mMotionEventSampleDataTail = NULL; + mWasDispatched = false; + return OK; +} + +status_t InputPublisher::publishInputEvent( + int32_t type, + int32_t deviceId, + int32_t nature) { + if (mPinned) { + LOGE("channel '%s' publisher ~ Attempted to publish a new event but publisher has " + "not yet been reset.", mChannel->getName().string()); + return INVALID_OPERATION; + } + + // Pin the region. + // We do not check for ASHMEM_NOT_PURGED because we don't care about the previous + // contents of the buffer so it does not matter whether it was purged in the meantime. + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_pin_region(ashmemFd, 0, 0); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d pinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mPinned = true; + + result = sem_init(& mSharedMessage->semaphore, 1, 1); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_init.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSemaphoreInitialized = true; + + mSharedMessage->consumed = false; + mSharedMessage->type = type; + mSharedMessage->deviceId = deviceId; + mSharedMessage->nature = nature; + return OK; +} + +status_t InputPublisher::publishKeyEvent( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t flags, + int32_t keyCode, + int32_t scanCode, + int32_t metaState, + int32_t repeatCount, + nsecs_t downTime, + nsecs_t eventTime) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, nature=%d, " + "action=%d, flags=%d, keyCode=%d, scanCode=%d, metaState=%d, repeatCount=%d," + "downTime=%lld, eventTime=%lld", + mChannel->getName().string(), + deviceId, nature, action, flags, keyCode, scanCode, metaState, repeatCount, + downTime, eventTime); +#endif + + status_t result = publishInputEvent(INPUT_EVENT_TYPE_KEY, deviceId, nature); + if (result < 0) { + return result; + } + + mSharedMessage->key.action = action; + mSharedMessage->key.flags = flags; + mSharedMessage->key.keyCode = keyCode; + mSharedMessage->key.scanCode = scanCode; + mSharedMessage->key.metaState = metaState; + mSharedMessage->key.repeatCount = repeatCount; + mSharedMessage->key.downTime = downTime; + mSharedMessage->key.eventTime = eventTime; + return OK; +} + +status_t InputPublisher::publishMotionEvent( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t edgeFlags, + int32_t metaState, + float xOffset, + float yOffset, + float xPrecision, + float yPrecision, + nsecs_t downTime, + nsecs_t eventTime, + size_t pointerCount, + const int32_t* pointerIds, + const PointerCoords* pointerCoords) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, nature=%d, " + "action=%d, edgeFlags=%d, metaState=%d, xOffset=%f, yOffset=%f, " + "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, " + "pointerCount=%d", + mChannel->getName().string(), + deviceId, nature, action, edgeFlags, metaState, xOffset, yOffset, + xPrecision, yPrecision, downTime, eventTime, pointerCount); +#endif + + if (pointerCount > MAX_POINTERS || pointerCount < 1) { + LOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.", + mChannel->getName().string(), pointerCount); + return BAD_VALUE; + } + + status_t result = publishInputEvent(INPUT_EVENT_TYPE_MOTION, deviceId, nature); + if (result < 0) { + return result; + } + + mSharedMessage->motion.action = action; + mSharedMessage->motion.edgeFlags = edgeFlags; + mSharedMessage->motion.metaState = metaState; + mSharedMessage->motion.xOffset = xOffset; + mSharedMessage->motion.yOffset = yOffset; + mSharedMessage->motion.xPrecision = xPrecision; + mSharedMessage->motion.yPrecision = yPrecision; + mSharedMessage->motion.downTime = downTime; + mSharedMessage->motion.pointerCount = pointerCount; + + mSharedMessage->motion.sampleCount = 1; + mSharedMessage->motion.sampleData[0].eventTime = eventTime; + + for (size_t i = 0; i < pointerCount; i++) { + mSharedMessage->motion.pointerIds[i] = pointerIds[i]; + mSharedMessage->motion.sampleData[0].coords[i] = pointerCoords[i]; + } + + // Cache essential information about the motion event to ensure that a malicious consumer + // cannot confuse the publisher by modifying the contents of the shared memory buffer while + // it is being updated. + if (action == MOTION_EVENT_ACTION_MOVE) { + mMotionEventPointerCount = pointerCount; + mMotionEventSampleDataStride = InputMessage::sampleDataStride(pointerCount); + mMotionEventSampleDataTail = InputMessage::sampleDataPtrIncrement( + mSharedMessage->motion.sampleData, mMotionEventSampleDataStride); + } else { + mMotionEventSampleDataTail = NULL; + } + return OK; +} + +status_t InputPublisher::appendMotionSample( + nsecs_t eventTime, + const PointerCoords* pointerCoords) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ appendMotionSample: eventTime=%lld", + mChannel->getName().string(), eventTime); +#endif + + if (! mPinned || ! mMotionEventSampleDataTail) { + LOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current " + "MOTION_EVENT_ACTION_MOVE event.", mChannel->getName().string()); + return INVALID_OPERATION; + } + + InputMessage::SampleData* newTail = InputMessage::sampleDataPtrIncrement( + mMotionEventSampleDataTail, mMotionEventSampleDataStride); + size_t newBytesUsed = reinterpret_cast<char*>(newTail) - + reinterpret_cast<char*>(mSharedMessage); + + if (newBytesUsed > mAshmemSize) { + LOGD("channel '%s' publisher ~ Cannot append motion sample because the shared memory " + "buffer is full. Buffer size: %d bytes, pointers: %d, samples: %d", + mChannel->getName().string(), + mAshmemSize, mMotionEventPointerCount, mSharedMessage->motion.sampleCount); + return NO_MEMORY; + } + + int result; + if (mWasDispatched) { + result = sem_trywait(& mSharedMessage->semaphore); + if (result < 0) { + if (errno == EAGAIN) { + // Only possible source of contention is the consumer having consumed (or being in the + // process of consuming) the message and left the semaphore count at 0. + LOGD("channel '%s' publisher ~ Cannot append motion sample because the message has " + "already been consumed.", mChannel->getName().string()); + return FAILED_TRANSACTION; + } else { + LOGE("channel '%s' publisher ~ Error %d in sem_trywait.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + } + + mMotionEventSampleDataTail->eventTime = eventTime; + for (size_t i = 0; i < mMotionEventPointerCount; i++) { + mMotionEventSampleDataTail->coords[i] = pointerCoords[i]; + } + mMotionEventSampleDataTail = newTail; + + mSharedMessage->motion.sampleCount += 1; + + if (mWasDispatched) { + result = sem_post(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_post.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + return OK; +} + +status_t InputPublisher::sendDispatchSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ sendDispatchSignal", + mChannel->getName().string()); +#endif + + mWasDispatched = true; + return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH); +} + +status_t InputPublisher::receiveFinishedSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ receiveFinishedSignal", + mChannel->getName().string()); +#endif + + char signal; + status_t result = mChannel->receiveSignal(& signal); + if (result) { + return result; + } + if (signal != INPUT_SIGNAL_FINISHED) { + LOGE("channel '%s' publisher ~ Received unexpected signal '%c' from consumer", + mChannel->getName().string(), signal); + return UNKNOWN_ERROR; + } + return OK; +} + +// --- InputConsumer --- + +InputConsumer::InputConsumer(const sp<InputChannel>& channel) : + mChannel(channel), mSharedMessage(NULL) { +} + +InputConsumer::~InputConsumer() { + if (mSharedMessage) { + munmap(mSharedMessage, mAshmemSize); + } +} + +status_t InputConsumer::initialize() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ initialize", + mChannel->getName().string()); +#endif + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_get_size_region(ashmemFd); + if (result < 0) { + LOGE("channel '%s' consumer ~ Error %d getting size of ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mAshmemSize = (size_t) result; + + mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); + if (! mSharedMessage) { + LOGE("channel '%s' consumer ~ mmap failed on ashmem fd %d.", + mChannel->getName().string(), ashmemFd); + return NO_MEMORY; + } + + return OK; +} + +status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** event) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ consume", + mChannel->getName().string()); +#endif + + *event = NULL; + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_pin_region(ashmemFd, 0, 0); + if (result != ASHMEM_NOT_PURGED) { + if (result == ASHMEM_WAS_PURGED) { + LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged " + "which probably indicates that the publisher and consumer are out of sync.", + mChannel->getName().string(), result, ashmemFd); + return INVALID_OPERATION; + } + + LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + if (mSharedMessage->consumed) { + LOGE("channel '%s' consumer ~ The current message has already been consumed.", + mChannel->getName().string()); + return INVALID_OPERATION; + } + + // Acquire but *never release* the semaphore. Contention on the semaphore is used to signal + // to the publisher that the message has been consumed (or is in the process of being + // consumed). Eventually the publisher will reinitialize the semaphore for the next message. + result = sem_wait(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' consumer ~ Error %d in sem_wait.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSharedMessage->consumed = true; + + switch (mSharedMessage->type) { + case INPUT_EVENT_TYPE_KEY: { + KeyEvent* keyEvent = factory->createKeyEvent(); + if (! keyEvent) return NO_MEMORY; + + populateKeyEvent(keyEvent); + + *event = keyEvent; + break; + } + + case INPUT_EVENT_TYPE_MOTION: { + MotionEvent* motionEvent = factory->createMotionEvent(); + if (! motionEvent) return NO_MEMORY; + + populateMotionEvent(motionEvent); + + *event = motionEvent; + break; + } + + default: + LOGE("channel '%s' consumer ~ Received message of unknown type %d", + mChannel->getName().string(), mSharedMessage->type); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t InputConsumer::sendFinishedSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ sendFinishedSignal", + mChannel->getName().string()); +#endif + + return mChannel->sendSignal(INPUT_SIGNAL_FINISHED); +} + +status_t InputConsumer::receiveDispatchSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ receiveDispatchSignal", + mChannel->getName().string()); +#endif + + char signal; + status_t result = mChannel->receiveSignal(& signal); + if (result) { + return result; + } + if (signal != INPUT_SIGNAL_DISPATCH) { + LOGE("channel '%s' consumer ~ Received unexpected signal '%c' from publisher", + mChannel->getName().string(), signal); + return UNKNOWN_ERROR; + } + return OK; +} + +void InputConsumer::populateKeyEvent(KeyEvent* keyEvent) const { + keyEvent->initialize( + mSharedMessage->deviceId, + mSharedMessage->nature, + mSharedMessage->key.action, + mSharedMessage->key.flags, + mSharedMessage->key.keyCode, + mSharedMessage->key.scanCode, + mSharedMessage->key.metaState, + mSharedMessage->key.repeatCount, + mSharedMessage->key.downTime, + mSharedMessage->key.eventTime); +} + +void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const { + motionEvent->initialize( + mSharedMessage->deviceId, + mSharedMessage->nature, + mSharedMessage->motion.action, + mSharedMessage->motion.edgeFlags, + mSharedMessage->motion.metaState, + mSharedMessage->motion.sampleData[0].coords[0].x, + mSharedMessage->motion.sampleData[0].coords[0].y, + mSharedMessage->motion.xPrecision, + mSharedMessage->motion.yPrecision, + mSharedMessage->motion.downTime, + mSharedMessage->motion.sampleData[0].eventTime, + mSharedMessage->motion.pointerCount, + mSharedMessage->motion.pointerIds, + mSharedMessage->motion.sampleData[0].coords); + + size_t sampleCount = mSharedMessage->motion.sampleCount; + if (sampleCount > 1) { + InputMessage::SampleData* sampleData = mSharedMessage->motion.sampleData; + size_t sampleDataStride = InputMessage::sampleDataStride( + mSharedMessage->motion.pointerCount); + + while (--sampleCount > 0) { + sampleData = InputMessage::sampleDataPtrIncrement(sampleData, sampleDataStride); + motionEvent->addSample(sampleData->eventTime, sampleData->coords); + } + } + + motionEvent->offsetLocation(mSharedMessage->motion.xOffset, + mSharedMessage->motion.yOffset); +} + +} // namespace android diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk index 6cc4a5a..018f18d 100644 --- a/libs/ui/tests/Android.mk +++ b/libs/ui/tests/Android.mk @@ -1,16 +1,38 @@ +# Build the unit tests. LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - region.cpp +test_src_files := \ + InputDispatcher_test.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ - libui + libEGL \ + libbinder \ + libpixelflinger \ + libhardware \ + libhardware_legacy \ + libui \ + libstlport -LOCAL_MODULE:= test-region +LOCAL_STATIC_LIBRARIES := \ + libgtest \ + libgtest_main -LOCAL_MODULE_TAGS := tests +LOCAL_C_INCLUDES := \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport -include $(BUILD_EXECUTABLE) +LOCAL_MODULE_TAGS := eng tests + +$(foreach file,$(test_src_files), \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) + +# Build the manual test programs. +include $(call all-subdir-makefiles) diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp new file mode 100644 index 0000000..3d92043 --- /dev/null +++ b/libs/ui/tests/InputDispatcher_test.cpp @@ -0,0 +1,19 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <ui/InputDispatcher.h> +#include <gtest/gtest.h> + +namespace android { + +class InputDispatcherTest : public testing::Test { +public: +}; + +TEST_F(InputDispatcherTest, Dummy) { + SCOPED_TRACE("Trace"); + ASSERT_FALSE(true); +} + +} // namespace android diff --git a/libs/ui/tests/region/Android.mk b/libs/ui/tests/region/Android.mk new file mode 100644 index 0000000..6cc4a5a --- /dev/null +++ b/libs/ui/tests/region/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + region.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libui + +LOCAL_MODULE:= test-region + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/libs/ui/tests/region.cpp b/libs/ui/tests/region/region.cpp index ef15de9..ef15de9 100644 --- a/libs/ui/tests/region.cpp +++ b/libs/ui/tests/region/region.cpp |