diff options
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/GraphicBuffer.cpp | 34 | ||||
-rw-r--r-- | libs/ui/GraphicBufferAllocator.cpp | 19 | ||||
-rw-r--r-- | libs/ui/Input.cpp | 253 | ||||
-rw-r--r-- | libs/ui/InputDispatcher.cpp | 1463 | ||||
-rw-r--r-- | libs/ui/InputManager.cpp | 105 | ||||
-rw-r--r-- | libs/ui/InputReader.cpp | 1979 | ||||
-rw-r--r-- | libs/ui/InputTransport.cpp | 688 | ||||
-rw-r--r-- | libs/ui/tests/Android.mk | 43 | ||||
-rw-r--r-- | libs/ui/tests/InputChannel_test.cpp | 158 | ||||
-rw-r--r-- | libs/ui/tests/InputDispatcher_test.cpp | 18 | ||||
-rw-r--r-- | libs/ui/tests/InputPublisherAndConsumer_test.cpp | 449 | ||||
-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 |
15 files changed, 5295 insertions, 107 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/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp index ba1fd9c..4b5f025 100644 --- a/libs/ui/GraphicBuffer.cpp +++ b/libs/ui/GraphicBuffer.cpp @@ -38,7 +38,7 @@ namespace android { GraphicBuffer::GraphicBuffer() : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()), - mInitCheck(NO_ERROR), mVStride(0), mIndex(-1) + mInitCheck(NO_ERROR), mIndex(-1) { width = height = @@ -51,7 +51,7 @@ GraphicBuffer::GraphicBuffer() GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h, PixelFormat reqFormat, uint32_t reqUsage) : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()), - mInitCheck(NO_ERROR), mVStride(0), mIndex(-1) + mInitCheck(NO_ERROR), mIndex(-1) { width = height = @@ -67,7 +67,7 @@ GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h, uint32_t inStride, native_handle_t* inHandle, bool keepOwnership) : BASE(), mOwner(keepOwnership ? ownHandle : ownNone), mBufferMapper(GraphicBufferMapper::get()), - mInitCheck(NO_ERROR), mVStride(0), mIndex(-1) + mInitCheck(NO_ERROR), mIndex(-1) { width = w; height = h; @@ -111,6 +111,9 @@ status_t GraphicBuffer::reallocate(uint32_t w, uint32_t h, PixelFormat f, if (mOwner != ownData) return INVALID_OPERATION; + if (handle && w==width && h==height && f==format && reqUsage==usage) + return NO_ERROR; + if (handle) { GraphicBufferAllocator& allocator(GraphicBufferAllocator::get()); allocator.free(handle); @@ -122,17 +125,25 @@ status_t GraphicBuffer::reallocate(uint32_t w, uint32_t h, PixelFormat f, status_t GraphicBuffer::initSize(uint32_t w, uint32_t h, PixelFormat format, uint32_t reqUsage) { - if (format == PIXEL_FORMAT_RGBX_8888) - format = PIXEL_FORMAT_RGBA_8888; - GraphicBufferAllocator& allocator = GraphicBufferAllocator::get(); status_t err = allocator.alloc(w, h, format, reqUsage, &handle, &stride); + + if (err<0 && format == PIXEL_FORMAT_RGBX_8888) { + /* + * There is currently a bug with some gralloc implementations + * not supporting RGBX_8888. In this case, we revert to using RGBA_8888 + * which is not exactly the same, as GL_REPLACE will yield a different + * result. + */ + format = PIXEL_FORMAT_RGBA_8888; + err = allocator.alloc(w, h, format, reqUsage, &handle, &stride); + } + if (err == NO_ERROR) { this->width = w; this->height = h; this->format = format; this->usage = reqUsage; - mVStride = 0; } return err; } @@ -173,7 +184,6 @@ status_t GraphicBuffer::lock(GGLSurface* sur, uint32_t usage) sur->height = height; sur->stride = stride; sur->format = format; - sur->vstride = mVStride; sur->data = static_cast<GGLubyte*>(vaddr); } return res; @@ -267,14 +277,6 @@ int GraphicBuffer::getIndex() const { return mIndex; } -void GraphicBuffer::setVerticalStride(uint32_t vstride) { - mVStride = vstride; -} - -uint32_t GraphicBuffer::getVerticalStride() const { - return mVStride; -} - // --------------------------------------------------------------------------- }; // namespace android diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp index 6ae7e74..d51664d 100644 --- a/libs/ui/GraphicBufferAllocator.cpp +++ b/libs/ui/GraphicBufferAllocator.cpp @@ -15,6 +15,8 @@ ** limitations under the License. */ +#define LOG_TAG "GraphicBufferAllocator" + #include <cutils/log.h> #include <utils/Singleton.h> @@ -61,9 +63,9 @@ void GraphicBufferAllocator::dump(String8& result) const const size_t c = list.size(); for (size_t i=0 ; i<c ; i++) { const alloc_rec_t& rec(list.valueAt(i)); - snprintf(buffer, SIZE, "%10p: %7.2f KiB | %4u x %4u | %2d | 0x%08x\n", + snprintf(buffer, SIZE, "%10p: %7.2f KiB | %4u (%4u) x %4u | %2d | 0x%08x\n", list.keyAt(i), rec.size/1024.0f, - rec.w, rec.h, rec.format, rec.usage); + rec.w, rec.s, rec.h, rec.format, rec.usage); result.append(buffer); total += rec.size; } @@ -71,16 +73,13 @@ void GraphicBufferAllocator::dump(String8& result) const result.append(buffer); } -static inline uint32_t clamp(uint32_t c) { - return c>0 ? c : 1; -} - status_t GraphicBufferAllocator::alloc(uint32_t w, uint32_t h, PixelFormat format, int usage, buffer_handle_t* handle, int32_t* stride) { - // make sure to not allocate a 0 x 0 buffer - w = clamp(w); - h = clamp(h); + // make sure to not allocate a N x 0 or 0 x N buffer, since this is + // allowed from an API stand-point allocate a 1x1 buffer instead. + if (!w || !h) + w = h = 1; // we have a h/w allocator and h/w buffer is requested status_t err; @@ -100,9 +99,9 @@ status_t GraphicBufferAllocator::alloc(uint32_t w, uint32_t h, PixelFormat forma alloc_rec_t rec; rec.w = w; rec.h = h; + rec.s = *stride; rec.format = format; rec.usage = usage; - rec.vaddr = 0; rec.size = h * stride[0] * bytesPerPixel(format); list.add(*handle, rec); } else { diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp new file mode 100644 index 0000000..0e6f2f5 --- /dev/null +++ b/libs/ui/Input.cpp @@ -0,0 +1,253 @@ +// +// 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 xOffset, + float yOffset, + 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; + mXOffset = xOffset; + mYOffset = yOffset; + 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) { + mXOffset += xOffset; + mYOffset += 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_offset(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getXOffset(); +} + +float motion_event_get_y_offset(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getYOffset(); +} + +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, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX(pointer_index); +} + +float motion_event_get_raw_y(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY(pointer_index); +} + +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_raw_x(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalRawX( + pointer_index, history_index); +} + +float motion_event_get_historical_raw_y(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalRawY( + pointer_index, 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..14dcada --- /dev/null +++ b/libs/ui/InputDispatcher.cpp @@ -0,0 +1,1463 @@ +// +// 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 registrations. +#define DEBUG_REGISTRATION 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 <errno.h> +#include <limits.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<InputDispatcherPolicyInterface>& 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; + + mCurrentInputTargetsValid = false; +} + +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() { + nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout(); + + bool skipPoll = false; + 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 (keyRepeatTimeout < 0) { + 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) { + processKeyRepeatLockedInterruptible(currentTime, keyRepeatTimeout); + skipPoll = true; + } else { + if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) { + nextWakeupTime = mKeyRepeatState.nextRepeatTime; + } + } + } + } else { + // Inbound queue has at least one entry. + // Start processing it but leave it on the queue until later so that the + // input reader can keep appending samples onto a motion event between the + // time we started processing it and the time we finally enqueue dispatch + // entries for it. + EventEntry* entry = mInboundQueue.head.next; + + switch (entry->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: { + ConfigurationChangedEntry* typedEntry = + static_cast<ConfigurationChangedEntry*>(entry); + processConfigurationChangedLockedInterruptible(currentTime, typedEntry); + break; + } + + case EventEntry::TYPE_KEY: { + KeyEntry* typedEntry = static_cast<KeyEntry*>(entry); + processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout); + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* typedEntry = static_cast<MotionEntry*>(entry); + processMotionLockedInterruptible(currentTime, typedEntry); + break; + } + + default: + assert(false); + break; + } + + // Dequeue and release the event entry that we just processed. + mInboundQueue.dequeue(entry); + mAllocator.releaseEventEntry(entry); + skipPoll = true; + } + } + + // Run any deferred commands. + skipPoll |= runCommandsLockedInterruptible(); + } // release lock + + // If we dispatched anything, don't poll just now. Wait for the next iteration. + // Contents may have shifted during flight. + if (skipPoll) { + return; + } + + // 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); +} + +bool InputDispatcher::runCommandsLockedInterruptible() { + if (mCommandQueue.isEmpty()) { + return false; + } + + do { + CommandEntry* commandEntry = mCommandQueue.dequeueAtHead(); + + Command command = commandEntry->command; + (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible' + + mAllocator.releaseCommandEntry(commandEntry); + } while (! mCommandQueue.isEmpty()); + return true; +} + +InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) { + CommandEntry* commandEntry = mAllocator.obtainCommandEntry(command); + mCommandQueue.enqueueAtTail(commandEntry); + return commandEntry; +} + +void InputDispatcher::processConfigurationChangedLockedInterruptible( + nsecs_t currentTime, ConfigurationChangedEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processConfigurationChanged - eventTime=%lld", entry->eventTime); +#endif + + mLock.unlock(); + + mPolicy->notifyConfigurationChanged(entry->eventTime); + + mLock.lock(); +} + +void InputDispatcher::processKeyLockedInterruptible( + nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout) { +#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 + keyRepeatTimeout; + } + mKeyRepeatState.lastKeyEntry = entry; + entry->refCount += 1; + } else { + resetKeyRepeatLocked(); + } + + identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry); +} + +void InputDispatcher::processKeyRepeatLockedInterruptible( + nsecs_t currentTime, nsecs_t keyRepeatTimeout) { + // 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 + keyRepeatTimeout; + +#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 + + identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry); +} + +void InputDispatcher::processMotionLockedInterruptible( + 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 + + identifyInputTargetsAndDispatchMotionLockedInterruptible(currentTime, entry); +} + +void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible( + nsecs_t currentTime, KeyEntry* entry) { +#if DEBUG_DISPATCH_CYCLE + LOGD("identifyInputTargetsAndDispatchKey"); +#endif + + entry->dispatchInProgress = true; + mCurrentInputTargetsValid = false; + mLock.unlock(); + + 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); + + mLock.lock(); + mCurrentInputTargetsValid = true; + + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); +} + +void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible( + nsecs_t currentTime, MotionEntry* entry) { +#if DEBUG_DISPATCH_CYCLE + LOGD("identifyInputTargetsAndDispatchMotion"); +#endif + + entry->dispatchInProgress = true; + mCurrentInputTargetsValid = false; + mLock.unlock(); + + mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action, + entry->edgeFlags, entry->metaState, + 0, 0, entry->xPrecision, entry->yPrecision, + entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds, + entry->firstSample.pointerCoords); + + mCurrentInputTargets.clear(); + mPolicy->getMotionEventTargets(& mReusableMotionEvent, entry->policyFlags, + mCurrentInputTargets); + + mLock.lock(); + mCurrentInputTargetsValid = true; + + 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 + + assert(eventEntry->dispatchInProgress); // should already have been set to true + + 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->getStatusLabel()); + 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 + || connection->status == Connection::STATUS_ZOMBIE) { + 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 + + // Clear the pending timeout. + connection->nextTimeoutTime = LONG_LONG_MAX; + + // Clear the outbound queue. + bool deactivated = ! connection->outboundQueue.isEmpty(); + if (deactivated) { + do { + DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead(); + mAllocator.releaseDispatchEntry(dispatchEntry); + } while (! connection->outboundQueue.isEmpty()); + + deactivateConnectionLocked(connection); + } + + // Handle the case where the connection appears to be unrecoverably broken. + // Ignore already broken or zombie connections. + if (broken) { + if (connection->status == Connection::STATUS_NORMAL + || connection->status == Connection::STATUS_NOT_RESPONDING) { + connection->status = Connection::STATUS_BROKEN; + + // Notify other system components. + onDispatchCycleBrokenLocked(currentTime, connection); + } + } + + return 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*/); + d->runCommandsLockedInterruptible(); + 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*/); + d->runCommandsLockedInterruptible(); + return false; // remove the callback + } + + d->finishDispatchCycleLocked(currentTime, connection.get()); + d->runCommandsLockedInterruptible(); + return true; + } // release lock +} + +void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime); +#endif + + bool wasEmpty; + { // acquire lock + AutoMutex _l(mLock); + + ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(); + newEntry->eventTime = eventTime; + + wasEmpty = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(newEntry); + } // release lock + + if (wasEmpty) { + mPollLoop->wake(); + } +} + +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. + 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 + + // Sanity check for special case because dispatch is interruptible. + // The dispatch logic is partially interruptible and releases its lock while + // identifying targets. However, as soon as the targets have been identified, + // the dispatcher proceeds to write a dispatch entry into all relevant outbound + // queues and then promptly removes the motion entry from the queue. + // + // Consequently, we should never observe the case where the inbound queue contains + // an in-progress motion entry unless the current input targets are invalid + // (currently being computed). Check for this! + assert(! (motionEntry->dispatchInProgress && mCurrentInputTargetsValid)); + + 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!). + if (mCurrentInputTargetsValid) { + 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*/); + + runCommandsLockedInterruptible(); + 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) { +#if DEBUG_REGISTRATION + LOGD("channel '%s' - Registered", inputChannel->getName().string()); +#endif + + 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); + + runCommandsLockedInterruptible(); + } // release lock + + mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); + return OK; +} + +status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) { +#if DEBUG_REGISTRATION + LOGD("channel '%s' - Unregistered", inputChannel->getName().string()); +#endif + + 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*/); + + runCommandsLockedInterruptible(); + } // 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)); + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible); + commandEntry->inputChannel = connection->inputChannel; + } +} + +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)); + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyInputChannelANRLockedInterruptible); + commandEntry->inputChannel = connection->inputChannel; +} + +void InputDispatcher::onDispatchCycleBrokenLocked( + nsecs_t currentTime, Connection* connection) { + LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!", + connection->getInputChannelName()); + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible); + commandEntry->inputChannel = connection->inputChannel; +} + +void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->notifyInputChannelBroken(commandEntry->inputChannel); + commandEntry->inputChannel.clear(); + + mLock.lock(); +} + +void InputDispatcher::doNotifyInputChannelANRLockedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->notifyInputChannelANR(commandEntry->inputChannel); + commandEntry->inputChannel.clear(); + + mLock.lock(); +} + +void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->notifyInputChannelRecoveredFromANR(commandEntry->inputChannel); + commandEntry->inputChannel.clear(); + + mLock.lock(); +} + + +// --- InputDispatcher::Allocator --- + +InputDispatcher::Allocator::Allocator() { +} + +InputDispatcher::ConfigurationChangedEntry* +InputDispatcher::Allocator::obtainConfigurationChangedEntry() { + ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_CONFIGURATION_CHANGED; + entry->dispatchInProgress = false; + return entry; +} + +InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry() { + KeyEntry* entry = mKeyEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_KEY; + entry->dispatchInProgress = false; + return entry; +} + +InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry() { + MotionEntry* entry = mMotionEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_MOTION; + entry->firstSample.next = NULL; + entry->dispatchInProgress = false; + return entry; +} + +InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry( + EventEntry* eventEntry) { + DispatchEntry* entry = mDispatchEntryPool.alloc(); + entry->eventEntry = eventEntry; + eventEntry->refCount += 1; + return entry; +} + +InputDispatcher::CommandEntry* InputDispatcher::Allocator::obtainCommandEntry(Command command) { + CommandEntry* entry = mCommandEntryPool.alloc(); + entry->command = command; + 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) { + for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) { + MotionSample* next = sample->next; + mMotionSamplePool.free(sample); + sample = next; + } + mMotionEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) { + releaseEventEntry(entry->eventEntry); + mDispatchEntryPool.free(entry); +} + +void InputDispatcher::Allocator::releaseCommandEntry(CommandEntry* entry) { + mCommandEntryPool.free(entry); +} + +void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, + nsecs_t eventTime, 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; +} + +// --- 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(); +} + +const char* InputDispatcher::Connection::getStatusLabel() const { + switch (status) { + case STATUS_NORMAL: + return "NORMAL"; + + case STATUS_BROKEN: + return "BROKEN"; + + case STATUS_NOT_RESPONDING: + return "NOT_RESPONDING"; + + case STATUS_ZOMBIE: + return "ZOMBIE"; + + default: + return "UNKNOWN"; + } +} + +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; +} + +// --- InputDispatcher::CommandEntry --- + +InputDispatcher::CommandEntry::CommandEntry() { +} + +InputDispatcher::CommandEntry::~CommandEntry() { +} + + +// --- 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..7538dd0 --- /dev/null +++ b/libs/ui/InputManager.cpp @@ -0,0 +1,105 @@ +// +// 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<InputReaderPolicyInterface>& readerPolicy, + const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { + mDispatcher = new InputDispatcher(dispatcherPolicy); + mReader = new InputReader(eventHub, readerPolicy, mDispatcher); + initialize(); +} + +InputManager::InputManager( + const sp<InputReaderInterface>& reader, + const sp<InputDispatcherInterface>& dispatcher) : + mReader(reader), + mDispatcher(dispatcher) { + initialize(); +} + +InputManager::~InputManager() { + stop(); +} + +void InputManager::initialize() { + mReaderThread = new InputReaderThread(mReader); + mDispatcherThread = new InputDispatcherThread(mDispatcher); +} + +status_t InputManager::start() { + status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputDispatcher thread due to error %d.", result); + return result; + } + + result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputReader thread due to error %d.", result); + + mDispatcherThread->requestExit(); + return result; + } + + return OK; +} + +status_t InputManager::stop() { + status_t result = mReaderThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputReader thread due to error %d.", result); + } + + result = mDispatcherThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputDispatcher thread due to error %d.", result); + } + + return OK; +} + +status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) { + return mDispatcher->registerInputChannel(inputChannel); +} + +status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) { + return mDispatcher->unregisterInputChannel(inputChannel); +} + +void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) const { + mReader->getCurrentInputConfiguration(outConfiguration); +} + +int32_t InputManager::getScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const { + return mReader->getCurrentScanCodeState(deviceId, deviceClasses, scanCode); +} + +int32_t InputManager::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const { + return mReader->getCurrentKeyCodeState(deviceId, deviceClasses, keyCode); +} + +int32_t InputManager::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const { + return mReader->getCurrentSwitchState(deviceId, deviceClasses, sw); +} + +bool InputManager::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { + return mReader->hasKeys(numCodes, keyCodes, outFlags); +} + +} // namespace android diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp new file mode 100644 index 0000000..5a280ae --- /dev/null +++ b/libs/ui/InputReader.cpp @@ -0,0 +1,1979 @@ +// +// 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 + +// Log debug messages about pointer assignment calculations. +#define DEBUG_POINTER_ASSIGNMENT 0 + +#include <cutils/log.h> +#include <ui/InputReader.h> + +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +/** Amount that trackball needs to move in order to generate a key event. */ +#define TRACKBALL_MOVEMENT_THRESHOLD 6 + +/* Slop distance for jumpy pointer detection. + * The vertical range of the screen divided by this is our epsilon value. */ +#define JUMPY_EPSILON_DIVISOR 212 + +/* Number of jumpy points to drop for touchscreens that need it. */ +#define JUMPY_TRANSITION_DROPS 3 +#define JUMPY_DROP_LIMIT 3 + +/* Maximum squared distance for averaging. + * If moving farther than this, turn of averaging to avoid lag in response. */ +#define AVERAGING_DISTANCE_LIMIT (75 * 75) + + +namespace android { + +// --- Static Functions --- + +template<typename T> +inline static T abs(const T& value) { + return value < 0 ? - value : value; +} + +template<typename T> +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +template<typename T> +inline static void swap(T& a, T& b) { + T temp = a; + a = b; + b = temp; +} + + +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 != InputReaderPolicyInterface::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; +} + +struct PointerDistanceHeapElement { + uint32_t currentPointerIndex : 8; + uint32_t lastPointerIndex : 8; + uint64_t distance : 48; // squared distance +}; + +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. + PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS]; + + uint32_t heapSize = 0; + for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; + currentPointerIndex++) { + for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount; + lastPointerIndex++) { + int64_t deltaX = 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). + heap[heapSize].currentPointerIndex = currentPointerIndex; + heap[heapSize].lastPointerIndex = lastPointerIndex; + heap[heapSize].distance = distance; + heapSize += 1; + } + } + + // Heapify + for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) { + startIndex -= 1; + for (uint32_t parentIndex = startIndex; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + } + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif + + // Pull matches out by increasing order of distance. + // To avoid reassigning pointers that have already been matched, the loop keeps track + // of which last and current pointers have been matched using the matchedXXXBits variables. + // It also tracks the used pointer id bits. + BitSet32 matchedLastBits(0); + BitSet32 matchedCurrentBits(0); + BitSet32 usedIdBits(0); + bool first = true; + for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) { + for (;;) { + if (first) { + // The first time through the loop, we just consume the root element of + // the heap (the one with smallest distance). + first = false; + } else { + // Previous iterations consumed the root element of the heap. + // Pop root element off of the heap (sift down). + heapSize -= 1; + assert(heapSize > 0); + + // Sift down. + heap[0] = heap[heapSize]; + for (uint32_t parentIndex = 0; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif + } + + uint32_t currentPointerIndex = heap[0].currentPointerIndex; + if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched + + uint32_t lastPointerIndex = heap[0].lastPointerIndex; + if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched + + matchedCurrentBits.markBit(currentPointerIndex); + matchedLastBits.markBit(lastPointerIndex); + + uint32_t id = lastTouch.pointers[lastPointerIndex].id; + currentTouch.pointers[currentPointerIndex].id = id; + currentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld", + lastPointerIndex, currentPointerIndex, id, heap[0].distance); +#endif + break; + } + } + + // Assign fresh ids to new pointers. + if (currentPointerCount > lastPointerCount) { + for (uint32_t i = currentPointerCount - lastPointerCount; ;) { + uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit(); + uint32_t id = usedIdBits.firstUnmarkedBit(); + + currentTouch.pointers[currentPointerIndex].id = id; + currentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - assigned: cur=%d, id=%d", + currentPointerIndex, id); +#endif + + if (--i == 0) break; // done + matchedCurrentBits.markBit(currentPointerIndex); + } + } + + // Fix id bits. + 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<InputReaderPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher) : + mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher) { + configureExcludedDevices(); + resetGlobalMetaState(); + resetDisplayProperties(); + updateExportedVirtualKeyState(); +} + +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->scanCode, rawEvent->value); +} + +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 & InputReaderPolicyInterface::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, int32_t switchCode, + int32_t switchValue) { + int32_t policyActions = mPolicy->interceptSwitch(when, switchCode, switchValue); + + uint32_t policyFlags = 0; + applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags); +} + +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) + || device->touchScreen.currentTouch.pointerCount != 1) { + // Pointer moved inside the display area or another pointer also went down. + // 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 == 1 + && 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(); + + updateExportedVirtualKeyState(); + + 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 InputReaderPolicyInterface::ROTATION_90: + case InputReaderPolicyInterface::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 InputReaderPolicyInterface::ROTATION_90: { + float xTemp = x; + x = y; + y = mDisplayHeight - xTemp; + break; + } + case InputReaderPolicyInterface::ROTATION_180: { + x = mDisplayWidth - x; + y = mDisplayHeight - y; + break; + } + case InputReaderPolicyInterface::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 InputReaderPolicyInterface::ROTATION_90: + temp = pointerCoords.x; + pointerCoords.x = pointerCoords.y; + pointerCoords.y = - temp; + break; + + case InputReaderPolicyInterface::ROTATION_180: + pointerCoords.x = - pointerCoords.x; + pointerCoords.y = - pointerCoords.y; + break; + + case InputReaderPolicyInterface::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. + updateExportedVirtualKeyState(); + + // Update input configuration. + updateExportedInputConfiguration(); + + // Enqueue configuration changed. + mDispatcher->notifyConfigurationChanged(when); +} + +bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when, + int32_t policyActions, uint32_t* policyFlags) { + if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) { + mDispatcher->notifyAppSwitchComing(when); + } + + if (policyActions & InputReaderPolicyInterface::ACTION_WOKE_HERE) { + *policyFlags |= POLICY_FLAG_WOKE_HERE; + } + + if (policyActions & InputReaderPolicyInterface::ACTION_BRIGHT_HERE) { + *policyFlags |= POLICY_FLAG_BRIGHT_HERE; + } + + return policyActions & InputReaderPolicyInterface::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<InputReaderPolicyInterface::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 InputReaderPolicyInterface::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::configureExcludedDevices() { + Vector<String8> excludedDeviceNames; + mPolicy->getExcludedDeviceNames(excludedDeviceNames); + + for (size_t i = 0; i < excludedDeviceNames.size(); i++) { + mEventHub->addExcludedDevice(excludedDeviceNames[i]); + } +} + +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::updateExportedVirtualKeyState() { + 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; + } + } + } + + { // acquire exported state lock + AutoMutex _l(mExportedStateLock); + + mExportedVirtualKeyCode = keyCode; + mExportedVirtualScanCode = scanCode; + } // release exported state lock +} + +bool InputReader::getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const { + { // acquire exported state lock + AutoMutex _l(mExportedStateLock); + + *outKeyCode = mExportedVirtualKeyCode; + *outScanCode = mExportedVirtualScanCode; + return mExportedVirtualKeyCode != -1; + } // release exported state lock +} + +void InputReader::updateExportedInputConfiguration() { + int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH; + int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS; + int32_t navigationConfig = InputConfiguration::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 = InputConfiguration::TOUCHSCREEN_FINGER; + } + if (deviceClasses & INPUT_DEVICE_CLASS_ALPHAKEY) { + keyboardConfig = InputConfiguration::KEYBOARD_QWERTY; + } + if (deviceClasses & INPUT_DEVICE_CLASS_TRACKBALL) { + navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL; + } else if (deviceClasses & INPUT_DEVICE_CLASS_DPAD) { + navigationConfig = InputConfiguration::NAVIGATION_DPAD; + } + } + + { // acquire exported state lock + AutoMutex _l(mExportedStateLock); + + mExportedInputConfiguration.touchScreen = touchScreenConfig; + mExportedInputConfiguration.keyboard = keyboardConfig; + mExportedInputConfiguration.navigation = navigationConfig; + } // release exported state lock +} + +void InputReader::getCurrentInputConfiguration(InputConfiguration* outConfiguration) const { + { // acquire exported state lock + AutoMutex _l(mExportedStateLock); + + *outConfiguration = mExportedInputConfiguration; + } // release exported state lock +} + +int32_t InputReader::getCurrentScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const { + { // acquire exported state lock + AutoMutex _l(mExportedStateLock); + + if (mExportedVirtualScanCode == scanCode) { + return KEY_STATE_VIRTUAL; + } + } // release exported state lock + + return mEventHub->getScanCodeState(deviceId, deviceClasses, scanCode); +} + +int32_t InputReader::getCurrentKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const { + { // acquire exported state lock + AutoMutex _l(mExportedStateLock); + + if (mExportedVirtualKeyCode == keyCode) { + return KEY_STATE_VIRTUAL; + } + } // release exported state lock + + return mEventHub->getKeyCodeState(deviceId, deviceClasses, keyCode); +} + +int32_t InputReader::getCurrentSwitchState(int32_t deviceId, int32_t deviceClasses, + int32_t sw) const { + return mEventHub->getSwitchState(deviceId, deviceClasses, sw); +} + +bool InputReader::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { + return mEventHub->hasKeys(numCodes, keyCodes, outFlags); +} + + +// --- 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..86bbd37 --- /dev/null +++ b/libs/ui/InputTransport.cpp @@ -0,0 +1,688 @@ +// +// 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 0 + +// Log debug messages whenever InputChannel objects are created/destroyed +#define DEBUG_CHANNEL_LIFECYCLE 0 + +// Log debug messages about transport actions (initialize, reset, publish, ...) +#define DEBUG_TRANSPORT_ACTIONS 0 + + +#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, + sp<InputChannel>& outServerChannel, sp<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.clear(); + outClientChannel.clear(); + 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 (nRead == 0) { // check for EOF +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ receive signal failed because peer was closed", mName.string()); +#endif + return DEAD_OBJECT; + } + + 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** outEvent) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ consume", + mChannel->getName().string()); +#endif + + *outEvent = 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); + + *outEvent = keyEvent; + break; + } + + case INPUT_EVENT_TYPE_MOTION: { + MotionEvent* motionEvent = factory->createMotionEvent(); + if (! motionEvent) return NO_MEMORY; + + populateMotionEvent(motionEvent); + + *outEvent = 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.xOffset, + mSharedMessage->motion.yOffset, + 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); + } + } +} + +} // namespace android diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk index 6cc4a5a..46d7493 100644 --- a/libs/ui/tests/Android.mk +++ b/libs/ui/tests/Android.mk @@ -1,16 +1,45 @@ +# Build the unit tests. LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - region.cpp +test_src_files := \ + InputChannel_test.cpp \ + InputDispatcher_test.cpp \ + InputPublisherAndConsumer_test.cpp -LOCAL_SHARED_LIBRARIES := \ +shared_libraries := \ libcutils \ libutils \ - libui + libEGL \ + libbinder \ + libpixelflinger \ + libhardware \ + libhardware_legacy \ + libui \ + libstlport -LOCAL_MODULE:= test-region +static_libraries := \ + libgtest \ + libgtest_main -LOCAL_MODULE_TAGS := tests +c_includes := \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport -include $(BUILD_EXECUTABLE) +module_tags := eng tests + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_C_INCLUDES := $(c_includes)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval LOCAL_MODULE_TAGS := $(module_tags)) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) + +# Build the manual test programs. +include $(call all-subdir-makefiles) diff --git a/libs/ui/tests/InputChannel_test.cpp b/libs/ui/tests/InputChannel_test.cpp new file mode 100644 index 0000000..6cec1c0 --- /dev/null +++ b/libs/ui/tests/InputChannel_test.cpp @@ -0,0 +1,158 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <ui/InputTransport.h> +#include <utils/Timers.h> +#include <utils/StopWatch.h> +#include <gtest/gtest.h> +#include <unistd.h> +#include <time.h> +#include <sys/mman.h> +#include <cutils/ashmem.h> + +#include "../../utils/tests/TestHelpers.h" + +namespace android { + +class InputChannelTest : public testing::Test { +protected: + virtual void SetUp() { } + virtual void TearDown() { } +}; + + +TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) { + // Our purpose here is to verify that the input channel destructor closes the + // file descriptors provided to it. One easy way is to provide it with one end + // of a pipe and to check for EPIPE on the other end after the channel is destroyed. + Pipe fakeAshmem, sendPipe, receivePipe; + + sp<InputChannel> inputChannel = new InputChannel(String8("channel name"), + fakeAshmem.sendFd, receivePipe.receiveFd, sendPipe.sendFd); + + EXPECT_STREQ("channel name", inputChannel->getName().string()) + << "channel should have provided name"; + EXPECT_EQ(fakeAshmem.sendFd, inputChannel->getAshmemFd()) + << "channel should have provided ashmem fd"; + EXPECT_EQ(receivePipe.receiveFd, inputChannel->getReceivePipeFd()) + << "channel should have provided receive pipe fd"; + EXPECT_EQ(sendPipe.sendFd, inputChannel->getSendPipeFd()) + << "channel should have provided send pipe fd"; + + inputChannel.clear(); // destroys input channel + + EXPECT_EQ(-EPIPE, fakeAshmem.readSignal()) + << "channel should have closed ashmem fd when destroyed"; + EXPECT_EQ(-EPIPE, receivePipe.writeSignal()) + << "channel should have closed receive pipe fd when destroyed"; + EXPECT_EQ(-EPIPE, sendPipe.readSignal()) + << "channel should have closed send pipe fd when destroyed"; + + // clean up fds of Pipe endpoints that were closed so we don't try to close them again + fakeAshmem.sendFd = -1; + receivePipe.receiveFd = -1; + sendPipe.sendFd = -1; +} + +TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + // Name + EXPECT_STREQ("channel name (server)", serverChannel->getName().string()) + << "server channel should have suffixed name"; + EXPECT_STREQ("channel name (client)", clientChannel->getName().string()) + << "client channel should have suffixed name"; + + // Ashmem uniqueness + EXPECT_NE(serverChannel->getAshmemFd(), clientChannel->getAshmemFd()) + << "server and client channel should have different ashmem fds because it was dup'd"; + + // Ashmem usability + ssize_t serverAshmemSize = ashmem_get_size_region(serverChannel->getAshmemFd()); + ssize_t clientAshmemSize = ashmem_get_size_region(clientChannel->getAshmemFd()); + uint32_t* serverAshmem = static_cast<uint32_t*>(mmap(NULL, serverAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, serverChannel->getAshmemFd(), 0)); + uint32_t* clientAshmem = static_cast<uint32_t*>(mmap(NULL, clientAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, clientChannel->getAshmemFd(), 0)); + ASSERT_TRUE(serverAshmem != NULL) + << "server channel ashmem should be mappable"; + ASSERT_TRUE(clientAshmem != NULL) + << "client channel ashmem should be mappable"; + *serverAshmem = 0xf00dd00d; + EXPECT_EQ(0xf00dd00d, *clientAshmem) + << "ashmem buffer should be shared by client and server"; + munmap(serverAshmem, serverAshmemSize); + munmap(clientAshmem, clientAshmemSize); + + // Server->Client communication + EXPECT_EQ(OK, serverChannel->sendSignal('S')) + << "server channel should be able to send signal to client channel"; + char signal; + EXPECT_EQ(OK, clientChannel->receiveSignal(& signal)) + << "client channel should be able to receive signal from server channel"; + EXPECT_EQ('S', signal) + << "client channel should receive the correct signal from server channel"; + + // Client->Server communication + EXPECT_EQ(OK, clientChannel->sendSignal('c')) + << "client channel should be able to send signal to server channel"; + EXPECT_EQ(OK, serverChannel->receiveSignal(& signal)) + << "server channel should be able to receive signal from client channel"; + EXPECT_EQ('c', signal) + << "server channel should receive the correct signal from client channel"; +} + +TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + char signal; + EXPECT_EQ(WOULD_BLOCK, clientChannel->receiveSignal(& signal)) + << "receiveSignal should have returned WOULD_BLOCK"; +} + +TEST_F(InputChannelTest, ReceiveSignal_WhenPeerClosed_ReturnsAnError) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + serverChannel.clear(); // close server channel + + char signal; + EXPECT_EQ(DEAD_OBJECT, clientChannel->receiveSignal(& signal)) + << "receiveSignal should have returned DEAD_OBJECT"; +} + +TEST_F(InputChannelTest, SendSignal_WhenPeerClosed_ReturnsAnError) { + sp<InputChannel> serverChannel, clientChannel; + + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + ASSERT_EQ(OK, result) + << "should have successfully opened a channel pair"; + + serverChannel.clear(); // close server channel + + EXPECT_EQ(DEAD_OBJECT, clientChannel->sendSignal('S')) + << "sendSignal should have returned DEAD_OBJECT"; +} + + +} // namespace android diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp new file mode 100644 index 0000000..1dc6e46 --- /dev/null +++ b/libs/ui/tests/InputDispatcher_test.cpp @@ -0,0 +1,18 @@ +// +// 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) { + // TODO +} + +} // namespace android diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp new file mode 100644 index 0000000..2d6b531 --- /dev/null +++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp @@ -0,0 +1,449 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <ui/InputTransport.h> +#include <utils/Timers.h> +#include <utils/StopWatch.h> +#include <gtest/gtest.h> +#include <unistd.h> +#include <time.h> +#include <sys/mman.h> +#include <cutils/ashmem.h> + +#include "../../utils/tests/TestHelpers.h" + +namespace android { + +class InputPublisherAndConsumerTest : public testing::Test { +protected: + sp<InputChannel> serverChannel, clientChannel; + InputPublisher* mPublisher; + InputConsumer* mConsumer; + PreallocatedInputEventFactory mEventFactory; + + virtual void SetUp() { + status_t result = InputChannel::openInputChannelPair(String8("channel name"), + serverChannel, clientChannel); + + mPublisher = new InputPublisher(serverChannel); + mConsumer = new InputConsumer(clientChannel); + } + + virtual void TearDown() { + if (mPublisher) { + delete mPublisher; + mPublisher = NULL; + } + + if (mConsumer) { + delete mConsumer; + mConsumer = NULL; + } + + serverChannel.clear(); + clientChannel.clear(); + } + + void Initialize(); + void PublishAndConsumeKeyEvent(); + void PublishAndConsumeMotionEvent( + size_t samplesToAppendBeforeDispatch = 0, + size_t samplesToAppendAfterDispatch = 0); +}; + +TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) { + EXPECT_EQ(serverChannel.get(), mPublisher->getChannel().get()); + EXPECT_EQ(clientChannel.get(), mConsumer->getChannel().get()); +} + +void InputPublisherAndConsumerTest::Initialize() { + status_t status; + + status = mPublisher->initialize(); + ASSERT_EQ(OK, status) + << "publisher initialize should return OK"; + + status = mConsumer->initialize(); + ASSERT_EQ(OK, status) + << "consumer initialize should return OK"; +} + +void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() { + status_t status; + + const int32_t deviceId = 1; + const int32_t nature = INPUT_EVENT_NATURE_KEY; + const int32_t action = KEY_EVENT_ACTION_DOWN; + const int32_t flags = KEY_EVENT_FLAG_FROM_SYSTEM; + const int32_t keyCode = KEYCODE_ENTER; + const int32_t scanCode = 13; + const int32_t metaState = META_ALT_LEFT_ON | META_ALT_ON; + const int32_t repeatCount = 1; + const nsecs_t downTime = 3; + const nsecs_t eventTime = 4; + + status = mPublisher->publishKeyEvent(deviceId, nature, action, flags, + keyCode, scanCode, metaState, repeatCount, downTime, eventTime); + ASSERT_EQ(OK, status) + << "publisher publishKeyEvent should return OK"; + + status = mPublisher->sendDispatchSignal(); + ASSERT_EQ(OK, status) + << "publisher sendDispatchSignal should return OK"; + + status = mConsumer->receiveDispatchSignal(); + ASSERT_EQ(OK, status) + << "consumer receiveDispatchSignal should return OK"; + + InputEvent* event; + status = mConsumer->consume(& mEventFactory, & event); + ASSERT_EQ(OK, status) + << "consumer consume should return OK"; + + ASSERT_TRUE(event != NULL) + << "consumer should have returned non-NULL event"; + ASSERT_EQ(INPUT_EVENT_TYPE_KEY, event->getType()) + << "consumer should have returned a key event"; + + KeyEvent* keyEvent = static_cast<KeyEvent*>(event); + EXPECT_EQ(deviceId, keyEvent->getDeviceId()); + EXPECT_EQ(nature, keyEvent->getNature()); + EXPECT_EQ(action, keyEvent->getAction()); + EXPECT_EQ(flags, keyEvent->getFlags()); + EXPECT_EQ(keyCode, keyEvent->getKeyCode()); + EXPECT_EQ(scanCode, keyEvent->getScanCode()); + EXPECT_EQ(metaState, keyEvent->getMetaState()); + EXPECT_EQ(repeatCount, keyEvent->getRepeatCount()); + EXPECT_EQ(downTime, keyEvent->getDownTime()); + EXPECT_EQ(eventTime, keyEvent->getEventTime()); + + status = mConsumer->sendFinishedSignal(); + ASSERT_EQ(OK, status) + << "consumer sendFinishedSignal should return OK"; + + status = mPublisher->receiveFinishedSignal(); + ASSERT_EQ(OK, status) + << "publisher receiveFinishedSignal should return OK"; + + status = mPublisher->reset(); + ASSERT_EQ(OK, status) + << "publisher reset should return OK"; +} + +void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent( + size_t samplesToAppendBeforeDispatch, size_t samplesToAppendAfterDispatch) { + status_t status; + + const int32_t deviceId = 1; + const int32_t nature = INPUT_EVENT_NATURE_TOUCH; + const int32_t action = MOTION_EVENT_ACTION_MOVE; + const int32_t edgeFlags = MOTION_EVENT_EDGE_FLAG_TOP; + const int32_t metaState = META_ALT_LEFT_ON | META_ALT_ON; + const float xOffset = -10; + const float yOffset = -20; + const float xPrecision = 0.25; + const float yPrecision = 0.5; + const nsecs_t downTime = 3; + const size_t pointerCount = 3; + const int32_t pointerIds[pointerCount] = { 2, 0, 1 }; + + Vector<nsecs_t> sampleEventTimes; + Vector<PointerCoords> samplePointerCoords; + + for (size_t i = 0; i <= samplesToAppendAfterDispatch + samplesToAppendBeforeDispatch; i++) { + sampleEventTimes.push(i + 10); + for (size_t j = 0; j < pointerCount; j++) { + samplePointerCoords.push(); + samplePointerCoords.editTop().x = 100 * i + j; + samplePointerCoords.editTop().y = 200 * i + j; + samplePointerCoords.editTop().pressure = 0.5 * i + j; + samplePointerCoords.editTop().size = 0.7 * i + j; + } + } + + status = mPublisher->publishMotionEvent(deviceId, nature, action, edgeFlags, + metaState, xOffset, yOffset, xPrecision, yPrecision, + downTime, sampleEventTimes[0], pointerCount, pointerIds, samplePointerCoords.array()); + ASSERT_EQ(OK, status) + << "publisher publishMotionEvent should return OK"; + + for (size_t i = 0; i < samplesToAppendBeforeDispatch; i++) { + size_t sampleIndex = i + 1; + status = mPublisher->appendMotionSample(sampleEventTimes[sampleIndex], + samplePointerCoords.array() + sampleIndex * pointerCount); + ASSERT_EQ(OK, status) + << "publisher appendMotionEvent should return OK"; + } + + status = mPublisher->sendDispatchSignal(); + ASSERT_EQ(OK, status) + << "publisher sendDispatchSignal should return OK"; + + for (size_t i = 0; i < samplesToAppendAfterDispatch; i++) { + size_t sampleIndex = i + 1 + samplesToAppendBeforeDispatch; + status = mPublisher->appendMotionSample(sampleEventTimes[sampleIndex], + samplePointerCoords.array() + sampleIndex * pointerCount); + ASSERT_EQ(OK, status) + << "publisher appendMotionEvent should return OK"; + } + + status = mConsumer->receiveDispatchSignal(); + ASSERT_EQ(OK, status) + << "consumer receiveDispatchSignal should return OK"; + + InputEvent* event; + status = mConsumer->consume(& mEventFactory, & event); + ASSERT_EQ(OK, status) + << "consumer consume should return OK"; + + ASSERT_TRUE(event != NULL) + << "consumer should have returned non-NULL event"; + ASSERT_EQ(INPUT_EVENT_TYPE_MOTION, event->getType()) + << "consumer should have returned a motion event"; + + size_t lastSampleIndex = samplesToAppendBeforeDispatch + samplesToAppendAfterDispatch; + + MotionEvent* motionEvent = static_cast<MotionEvent*>(event); + EXPECT_EQ(deviceId, motionEvent->getDeviceId()); + EXPECT_EQ(nature, motionEvent->getNature()); + EXPECT_EQ(action, motionEvent->getAction()); + EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags()); + EXPECT_EQ(metaState, motionEvent->getMetaState()); + EXPECT_EQ(xPrecision, motionEvent->getXPrecision()); + EXPECT_EQ(yPrecision, motionEvent->getYPrecision()); + EXPECT_EQ(downTime, motionEvent->getDownTime()); + EXPECT_EQ(sampleEventTimes[lastSampleIndex], motionEvent->getEventTime()); + EXPECT_EQ(pointerCount, motionEvent->getPointerCount()); + EXPECT_EQ(lastSampleIndex, motionEvent->getHistorySize()); + + for (size_t i = 0; i < pointerCount; i++) { + SCOPED_TRACE(i); + EXPECT_EQ(pointerIds[i], motionEvent->getPointerId(i)); + } + + for (size_t sampleIndex = 0; sampleIndex < lastSampleIndex; sampleIndex++) { + SCOPED_TRACE(sampleIndex); + EXPECT_EQ(sampleEventTimes[sampleIndex], + motionEvent->getHistoricalEventTime(sampleIndex)); + for (size_t i = 0; i < pointerCount; i++) { + SCOPED_TRACE(i); + size_t offset = sampleIndex * pointerCount + i; + EXPECT_EQ(samplePointerCoords[offset].x, + motionEvent->getHistoricalRawX(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].y, + motionEvent->getHistoricalRawY(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].x + xOffset, + motionEvent->getHistoricalX(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].y + yOffset, + motionEvent->getHistoricalY(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].pressure, + motionEvent->getHistoricalPressure(i, sampleIndex)); + EXPECT_EQ(samplePointerCoords[offset].size, + motionEvent->getHistoricalSize(i, sampleIndex)); + } + } + + SCOPED_TRACE(lastSampleIndex); + EXPECT_EQ(sampleEventTimes[lastSampleIndex], motionEvent->getEventTime()); + for (size_t i = 0; i < pointerCount; i++) { + SCOPED_TRACE(i); + size_t offset = lastSampleIndex * pointerCount + i; + EXPECT_EQ(samplePointerCoords[offset].x, motionEvent->getRawX(i)); + EXPECT_EQ(samplePointerCoords[offset].y, motionEvent->getRawY(i)); + EXPECT_EQ(samplePointerCoords[offset].x + xOffset, motionEvent->getX(i)); + EXPECT_EQ(samplePointerCoords[offset].y + yOffset, motionEvent->getY(i)); + EXPECT_EQ(samplePointerCoords[offset].pressure, motionEvent->getPressure(i)); + EXPECT_EQ(samplePointerCoords[offset].size, motionEvent->getSize(i)); + } + + status = mConsumer->sendFinishedSignal(); + ASSERT_EQ(OK, status) + << "consumer sendFinishedSignal should return OK"; + + status = mPublisher->receiveFinishedSignal(); + ASSERT_EQ(OK, status) + << "publisher receiveFinishedSignal should return OK"; + + status = mPublisher->reset(); + ASSERT_EQ(OK, status) + << "publisher reset should return OK"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); +} + +TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_WhenNotReset_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + status = mPublisher->publishKeyEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + ASSERT_EQ(OK, status) + << "publisher publishKeyEvent should return OK first time"; + + status = mPublisher->publishKeyEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher publishKeyEvent should return INVALID_OPERATION because " + "the publisher was not reset"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenNotReset_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = 1; + int32_t pointerIds[pointerCount] = { 0 }; + PointerCoords pointerCoords[pointerCount] = { { 0, 0, 0, 0 } }; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status) + << "publisher publishMotionEvent should return OK"; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher publishMotionEvent should return INVALID_OPERATION because "; + "the publisher was not reset"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = 0; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(BAD_VALUE, status) + << "publisher publishMotionEvent should return BAD_VALUE"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS + 1; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(BAD_VALUE, status) + << "publisher publishMotionEvent should return BAD_VALUE"; +} + +TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenCalledBeforeDispatchSignal_AppendsSamples) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent(3, 0)); +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenCalledAfterDispatchSignalAndNotConsumed_AppendsSamples) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent(0, 4)); +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenNoMotionEventPublished_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + PointerCoords pointerCoords[1]; + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher appendMotionSample should return INVALID_OPERATION"; +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenPublishedMotionEventIsNotAMove_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_DOWN, + 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status); + + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(INVALID_OPERATION, status) + << "publisher appendMotionSample should return INVALID_OPERATION"; +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenAlreadyConsumed_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_MOVE, + 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status); + + status = mPublisher->sendDispatchSignal(); + ASSERT_EQ(OK, status); + + status = mConsumer->receiveDispatchSignal(); + ASSERT_EQ(OK, status); + + InputEvent* event; + status = mConsumer->consume(& mEventFactory, & event); + ASSERT_EQ(OK, status); + + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(status_t(FAILED_TRANSACTION), status) + << "publisher appendMotionSample should return FAILED_TRANSACTION"; +} + +TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenBufferFull_ReturnsError) { + status_t status; + ASSERT_NO_FATAL_FAILURE(Initialize()); + + const size_t pointerCount = MAX_POINTERS; + int32_t pointerIds[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_MOVE, + 0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords); + ASSERT_EQ(OK, status); + + for (int count = 1;; count++) { + ASSERT_LT(count, 100000) << "should eventually reach OOM"; + + status = mPublisher->appendMotionSample(0, pointerCoords); + if (status != OK) { + ASSERT_GT(count, 12) << "should be able to add at least a dozen samples"; + ASSERT_EQ(NO_MEMORY, status) + << "publisher appendMotionSample should return NO_MEMORY when buffer is full"; + break; + } + } + + status = mPublisher->appendMotionSample(0, pointerCoords); + ASSERT_EQ(NO_MEMORY, status) + << "publisher appendMotionSample should return NO_MEMORY persistently until reset"; +} + +} // 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 |