diff options
author | Jeff Brown <jeffbrown@google.com> | 2011-03-09 17:39:48 -0800 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2011-03-14 14:12:03 -0700 |
commit | ace13b17866dc9136aeecf6dfaf7077f37434469 (patch) | |
tree | 0ec5d01ef83b4d5148525f5befa28ca026b294c7 | |
parent | fd10d5cf56e5b1ba7692400e4fe4ae26b61f3285 (diff) | |
download | frameworks_base-ace13b17866dc9136aeecf6dfaf7077f37434469.zip frameworks_base-ace13b17866dc9136aeecf6dfaf7077f37434469.tar.gz frameworks_base-ace13b17866dc9136aeecf6dfaf7077f37434469.tar.bz2 |
Use touch pad gestures to manipulate the pointer.
1. Single finger tap performs a click.
2. Single finger movement moves the pointer (hovers).
3. Button press plus movement performs click or drag.
While dragging, the pointer follows the finger that is moving
fastest. This is important if there are additional fingers
down on the touch pad for the purpose of applying force
to an integrated button underneath.
4. Two fingers near each other moving in the same direction
are coalesced as a swipe gesture under the pointer.
5. Two or more fingers moving in arbitrary directions are
transformed into touches in the vicinity of the pointer.
This makes scale/zoom and rotate gestures possible.
Added a native VelocityTracker implementation to enable intelligent
switching of the active pointer during drags.
Change-Id: I5ada57e7f2bdb9b0a791843eb354a8c706b365dc
-rw-r--r-- | include/ui/Input.h | 55 | ||||
-rw-r--r-- | include/utils/BitSet.h | 6 | ||||
-rw-r--r-- | libs/ui/Input.cpp | 154 | ||||
-rw-r--r-- | libs/ui/InputTransport.cpp | 4 | ||||
-rw-r--r-- | services/input/InputDispatcher.cpp | 14 | ||||
-rw-r--r-- | services/input/InputReader.cpp | 1440 | ||||
-rw-r--r-- | services/input/InputReader.h | 180 | ||||
-rw-r--r-- | services/input/tests/InputReader_test.cpp | 12 |
8 files changed, 1601 insertions, 264 deletions
diff --git a/include/ui/Input.h b/include/ui/Input.h index d9d77c4..e9cacf4 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -27,6 +27,7 @@ #include <utils/Timers.h> #include <utils/RefBase.h> #include <utils/String8.h> +#include <utils/BitSet.h> #ifdef HAVE_ANDROID_OS class SkMatrix; @@ -208,6 +209,13 @@ struct PointerCoords { status_t writeToParcel(Parcel* parcel) const; #endif + bool operator==(const PointerCoords& other) const; + inline bool operator!=(const PointerCoords& other) const { + return !(*this == other); + } + + void copyFrom(const PointerCoords& other); + private: void tooManyAxes(int axis); }; @@ -543,6 +551,53 @@ private: }; /* + * Calculates the velocity of pointer motions over time. + * Uses essentially the same algorithm as android.view.VelocityTracker. + */ +class VelocityTracker { +public: + struct Position { + float x, y; + }; + + VelocityTracker(); + + // Resets the velocity tracker state. + void clear(); + + // Adds movement information for a set of pointers. + // The idBits bitfield specifies the pointer ids of the pointers whose positions + // are included in the movement. + // The positions array contains position information for each pointer in order by + // increasing id. Its size should be equal to the number of one bits in idBits. + void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions); + + // Gets the velocity of the specified pointer id in position units per second. + // Returns false and sets the velocity components to zero if there is no movement + // information for the pointer. + bool getVelocity(uint32_t id, float* outVx, float* outVy) const; + +private: + // Number of samples to keep. + static const uint32_t HISTORY_SIZE = 10; + + // Oldest sample to consider when calculating the velocity. + static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms + + // The minimum duration between samples when estimating velocity. + static const nsecs_t MIN_DURATION = 5 * 1000000; // 5 ms + + struct Movement { + nsecs_t eventTime; + BitSet32 idBits; + Position positions[MAX_POINTERS]; + }; + + uint32_t mIndex; + Movement mMovements[HISTORY_SIZE]; +}; + +/* * Describes the characteristics and capabilities of an input device. */ class InputDeviceInfo { diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h index f5dbcd9..f03825a 100644 --- a/include/utils/BitSet.h +++ b/include/utils/BitSet.h @@ -61,6 +61,12 @@ struct BitSet32 { // Result is undefined if all bits are marked. inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); } + // Gets the index of the specified bit in the set, which is the number of + // marked bits that appear before the specified bit. + inline uint32_t getIndexOfBit(uint32_t n) const { + return __builtin_popcount(value & ~(0xffffffffUL >> n)); + } + inline bool operator== (const BitSet32& other) const { return value == other.value; } inline bool operator!= (const BitSet32& other) const { return value != other.value; } }; diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp index e2e698e..eaa8926 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -7,8 +7,12 @@ //#define LOG_NDEBUG 0 +// Log debug messages about keymap probing. #define DEBUG_PROBE 0 +// Log debug messages about velocity tracking. +#define DEBUG_VELOCITY 0 + #include <stdlib.h> #include <unistd.h> #include <ctype.h> @@ -329,6 +333,27 @@ void PointerCoords::tooManyAxes(int axis) { "cannot contain more than %d axis values.", axis, int(MAX_AXES)); } +bool PointerCoords::operator==(const PointerCoords& other) const { + if (bits != other.bits) { + return false; + } + uint32_t count = __builtin_popcountll(bits); + for (uint32_t i = 0; i < count; i++) { + if (values[i] != other.values[i]) { + return false; + } + } + return true; +} + +void PointerCoords::copyFrom(const PointerCoords& other) { + bits = other.bits; + uint32_t count = __builtin_popcountll(bits); + for (uint32_t i = 0; i < count; i++) { + values[i] = other.values[i]; + } +} + // --- MotionEvent --- @@ -634,6 +659,135 @@ bool MotionEvent::isTouchEvent(int32_t source, int32_t action) { } +// --- VelocityTracker --- + +VelocityTracker::VelocityTracker() { + clear(); +} + +void VelocityTracker::clear() { + mIndex = 0; + mMovements[0].idBits.clear(); +} + +void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) { + if (++mIndex == HISTORY_SIZE) { + mIndex = 0; + } + Movement& movement = mMovements[mIndex]; + movement.eventTime = eventTime; + movement.idBits = idBits; + uint32_t count = idBits.count(); + for (uint32_t i = 0; i < count; i++) { + movement.positions[i] = positions[i]; + } + +#if DEBUG_VELOCITY + LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x", eventTime, idBits.value); + for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) { + uint32_t id = iterBits.firstMarkedBit(); + uint32_t index = idBits.getIndexOfBit(id); + iterBits.clearBit(id); + float vx, vy; + bool available = getVelocity(id, &vx, &vy); + if (available) { + LOGD(" %d: position (%0.3f, %0.3f), velocity (%0.3f, %0.3f), speed %0.3f", + id, positions[index].x, positions[index].y, vx, vy, sqrtf(vx * vx + vy * vy)); + } else { + assert(vx == 0 && vy == 0); + LOGD(" %d: position (%0.3f, %0.3f), velocity not available", + id, positions[index].x, positions[index].y); + } + } +#endif +} + +bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { + const Movement& newestMovement = mMovements[mIndex]; + if (newestMovement.idBits.hasBit(id)) { + // Find the oldest sample that contains the pointer and that is not older than MAX_AGE. + nsecs_t minTime = newestMovement.eventTime - MAX_AGE; + uint32_t oldestIndex = mIndex; + uint32_t numTouches = 1; + do { + uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1; + const Movement& nextOldestMovement = mMovements[nextOldestIndex]; + if (!nextOldestMovement.idBits.hasBit(id) + || nextOldestMovement.eventTime < minTime) { + break; + } + oldestIndex = nextOldestIndex; + } while (++numTouches < HISTORY_SIZE); + + // If we have a lot of samples, skip the last received sample since it is + // probably pretty noisy compared to the sum of all of the traces already acquired. + // + // NOTE: This condition exists in the android.view.VelocityTracker and imposes a + // bias against the most recent data. + if (numTouches > 3) { + numTouches -= 1; + } + + // Calculate an exponentially weighted moving average of the velocity at different + // points in time measured relative to the oldest samples. This is essentially + // an IIR filter. + // + // One problem with this algorithm is that the sample data may be poorly conditioned. + // Sometimes samples arrive very close together in time which can cause us to + // overestimate the velocity at that time point. Most samples might be measured + // 16ms apart but some consecutive samples could be only 0.5sm apart due to + // the way they are reported by the hardware or driver (sometimes in bursts or with + // significant jitter). The instantaneous velocity for those samples 0.5ms apart will + // be calculated to be 32 times what it should have been. + // To work around this effect, we impose a minimum duration on the samples. + // + // FIXME: Samples close together in time can have an disproportionately large + // impact on the result because all samples are equally weighted. The average should + // instead take the time factor into account. + // + // FIXME: The minimum duration condition does not exist in + // android.view.VelocityTracker yet. It is less important there because sample times + // are truncated to the millisecond so back to back samples will often appear to be + // zero milliseconds apart and will be ignored if they are the oldest ones. + float accumVx = 0; + float accumVy = 0; + uint32_t index = oldestIndex; + uint32_t samplesUsed = 0; + const Movement& oldestMovement = mMovements[oldestIndex]; + const Position& oldestPosition = + oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)]; + while (numTouches-- > 1) { + if (++index == HISTORY_SIZE) { + index = 0; + } + const Movement& movement = mMovements[index]; + nsecs_t duration = movement.eventTime - oldestMovement.eventTime; + if (duration > MIN_DURATION) { + const Position& position = movement.positions[movement.idBits.getIndexOfBit(id)]; + float scale = 1000000000.0f / duration; // one over time delta in seconds + float vx = (position.x - oldestPosition.x) * scale; + float vy = (position.y - oldestPosition.y) * scale; + accumVx = accumVx == 0 ? vx : (accumVx + vx) * 0.5f; + accumVy = accumVy == 0 ? vy : (accumVy + vy) * 0.5f; + samplesUsed += 1; + } + } + + // Make sure we used at least one sample. + if (samplesUsed != 0) { + *outVx = accumVx; + *outVy = accumVy; + return true; + } + } + + // No data available for this pointer. + *outVx = 0; + *outVy = 0; + return false; +} + + // --- InputDeviceInfo --- InputDeviceInfo::InputDeviceInfo() { diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp index 5c57a76..9d1b8b9 100644 --- a/libs/ui/InputTransport.cpp +++ b/libs/ui/InputTransport.cpp @@ -406,7 +406,7 @@ status_t InputPublisher::publishMotionEvent( for (size_t i = 0; i < pointerCount; i++) { mSharedMessage->motion.pointerIds[i] = pointerIds[i]; - mSharedMessage->motion.sampleData[0].coords[i] = pointerCoords[i]; + mSharedMessage->motion.sampleData[0].coords[i].copyFrom(pointerCoords[i]); } // Cache essential information about the motion event to ensure that a malicious consumer @@ -475,7 +475,7 @@ status_t InputPublisher::appendMotionSample( mMotionEventSampleDataTail->eventTime = eventTime; for (size_t i = 0; i < mMotionEventPointerCount; i++) { - mMotionEventSampleDataTail->coords[i] = pointerCoords[i]; + mMotionEventSampleDataTail->coords[i].copyFrom(pointerCoords[i]); } mMotionEventSampleDataTail = newTail; diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 19295e6..ff26fc9 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -2135,8 +2135,8 @@ InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet if (pointerIds.hasBit(pointerId)) { splitPointerIndexMap[splitPointerCount] = originalPointerIndex; splitPointerIds[splitPointerCount] = pointerId; - splitPointerCoords[splitPointerCount] = - originalMotionEntry->firstSample.pointerCoords[originalPointerIndex]; + splitPointerCoords[splitPointerCount].copyFrom( + originalMotionEntry->firstSample.pointerCoords[originalPointerIndex]); splitPointerCount += 1; } } @@ -2199,8 +2199,8 @@ InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount; splitPointerIndex++) { uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex]; - splitPointerCoords[splitPointerIndex] = - originalMotionSample->pointerCoords[originalPointerIndex]; + splitPointerCoords[splitPointerIndex].copyFrom( + originalMotionSample->pointerCoords[originalPointerIndex]); } mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime, @@ -3453,7 +3453,7 @@ InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsec entry->lastSample = & entry->firstSample; for (uint32_t i = 0; i < pointerCount; i++) { entry->pointerIds[i] = pointerIds[i]; - entry->firstSample.pointerCoords[i] = pointerCoords[i]; + entry->firstSample.pointerCoords[i].copyFrom(pointerCoords[i]); } return entry; } @@ -3556,7 +3556,7 @@ void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, sample->eventTime = eventTime; uint32_t pointerCount = motionEntry->pointerCount; for (uint32_t i = 0; i < pointerCount; i++) { - sample->pointerCoords[i] = pointerCoords[i]; + sample->pointerCoords[i].copyFrom(pointerCoords[i]); } sample->next = NULL; @@ -3693,7 +3693,7 @@ void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* pointerCount = entry->pointerCount; for (uint32_t i = 0; i < entry->pointerCount; i++) { pointerIds[i] = entry->pointerIds[i]; - pointerCoords[i] = entry->lastSample->pointerCoords[i]; + pointerCoords[i].copyFrom(entry->lastSample->pointerCoords[i]); } } diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index 3029028..592939f 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -33,6 +33,9 @@ // Log debug messages about pointer assignment calculations. #define DEBUG_POINTER_ASSIGNMENT 0 +// Log debug messages about gesture detection. +#define DEBUG_GESTURES 0 + #include "InputReader.h" @@ -54,6 +57,38 @@ namespace android { +// --- Constants --- + +// Quiet time between certain gesture transitions. +// Time to allow for all fingers or buttons to settle into a stable state before +// starting a new gesture. +static const nsecs_t QUIET_INTERVAL = 100 * 1000000; // 100 ms + +// The minimum speed that a pointer must travel for us to consider switching the active +// touch pointer to it during a drag. This threshold is set to avoid switching due +// to noise from a finger resting on the touch pad (perhaps just pressing it down). +static const float DRAG_MIN_SWITCH_SPEED = 50.0f; // pixels per second + +// Tap gesture delay time. +// The time between down and up must be less than this to be considered a tap. +static const nsecs_t TAP_INTERVAL = 100 * 1000000; // 100 ms + +// The distance in pixels that the pointer is allowed to move from initial down +// to up and still be called a tap. +static const float TAP_SLOP = 5.0f; // 5 pixels + +// The transition from INDETERMINATE_MULTITOUCH to SWIPE or FREEFORM gesture mode is made when +// all of the pointers have traveled this number of pixels from the start point. +static const float MULTITOUCH_MIN_TRAVEL = 5.0f; + +// The transition from INDETERMINATE_MULTITOUCH to SWIPE gesture mode can only occur when the +// cosine of the angle between the two vectors is greater than or equal to than this value +// which indicates that the vectors are oriented in the same direction. +// When the vectors are oriented in the exactly same direction, the cosine is 1.0. +// (In exactly opposite directions, the cosine is -1.0.) +static const float SWIPE_TRANSITION_ANGLE_COSINE = 0.5f; // cosine of 45 degrees + + // --- Static Functions --- template<typename T> @@ -81,6 +116,12 @@ inline static float pythag(float x, float y) { return sqrtf(x * x + y * y); } +inline static int32_t distanceSquared(int32_t x1, int32_t y1, int32_t x2, int32_t y2) { + int32_t dx = x1 - x2; + int32_t dy = y1 - y2; + return dx * dx + dy * dy; +} + inline static int32_t signExtendNybble(int32_t value) { return value >= 8 ? value - 16 : value; } @@ -207,7 +248,7 @@ void InputReader::loopOnce() { mEventHub->getEvent(& rawEvent); #if DEBUG_RAW_EVENTS - LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d", + LOGD("Input event: device=%d type=0x%04x scancode=0x%04x keycode=0x%04x value=0x%04x", rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode, rawEvent.value); #endif @@ -1540,7 +1581,7 @@ TouchInputMapper::~TouchInputMapper() { } uint32_t TouchInputMapper::getSources() { - return mTouchSource; + return mTouchSource | mPointerSource; } void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { @@ -1579,6 +1620,18 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { if (mLocked.orientedRanges.haveOrientation) { info->addMotionRange(mLocked.orientedRanges.orientation); } + + if (mPointerController != NULL) { + float minX, minY, maxX, maxY; + if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mPointerSource, + minX, maxX, 0.0f, 0.0f); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mPointerSource, + minY, maxY, 0.0f, 0.0f); + } + info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mPointerSource, + 0.0f, 1.0f, 0.0f, 0.0f); + } } // release lock } @@ -1608,6 +1661,21 @@ void TouchInputMapper::dump(String8& dump) { dump.appendFormat(INDENT3 "Last Touch:\n"); dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount); + dump.appendFormat(INDENT4 "Button State: 0x%08x\n", mLastTouch.buttonState); + + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n"); + dump.appendFormat(INDENT4 "XMovementScale: %0.3f\n", + mLocked.pointerGestureXMovementScale); + dump.appendFormat(INDENT4 "YMovementScale: %0.3f\n", + mLocked.pointerGestureYMovementScale); + dump.appendFormat(INDENT4 "XZoomScale: %0.3f\n", + mLocked.pointerGestureXZoomScale); + dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n", + mLocked.pointerGestureYZoomScale); + dump.appendFormat(INDENT4 "MaxSwipeWidthSquared: %d\n", + mLocked.pointerGestureMaxSwipeWidthSquared); + } } // release lock } @@ -1630,6 +1698,8 @@ void TouchInputMapper::initializeLocked() { mLocked.orientedRanges.haveTouchSize = false; mLocked.orientedRanges.haveToolSize = false; mLocked.orientedRanges.haveOrientation = false; + + mPointerGesture.reset(); } void TouchInputMapper::configure() { @@ -1642,9 +1712,15 @@ void TouchInputMapper::configure() { switch (mParameters.deviceType) { case Parameters::DEVICE_TYPE_TOUCH_SCREEN: mTouchSource = AINPUT_SOURCE_TOUCHSCREEN; + mPointerSource = 0; break; case Parameters::DEVICE_TYPE_TOUCH_PAD: mTouchSource = AINPUT_SOURCE_TOUCHPAD; + mPointerSource = 0; + break; + case Parameters::DEVICE_TYPE_POINTER: + mTouchSource = AINPUT_SOURCE_TOUCHPAD; + mPointerSource = AINPUT_SOURCE_MOUSE; break; default: assert(false); @@ -1671,14 +1747,26 @@ void TouchInputMapper::configureParameters() { mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents(); mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime(); + if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X) + || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) { + // The device is a cursor device with a touch pad attached. + // By default don't use the touch pad to move the pointer. + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + } else { + // The device is just a touch pad. + // By default use the touch pad to move the pointer and to perform related gestures. + mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER; + } + String8 deviceTypeString; - mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"), deviceTypeString)) { if (deviceTypeString == "touchScreen") { mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN; } else if (deviceTypeString == "touchPad") { mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + } else if (deviceTypeString == "pointer") { + mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER; } else { LOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string()); } @@ -1690,6 +1778,7 @@ void TouchInputMapper::configureParameters() { mParameters.associatedDisplayId = mParameters.orientationAware || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER ? 0 : -1; } @@ -1703,6 +1792,9 @@ void TouchInputMapper::dumpParameters(String8& dump) { case Parameters::DEVICE_TYPE_TOUCH_PAD: dump.append(INDENT4 "DeviceType: touchPad\n"); break; + case Parameters::DEVICE_TYPE_POINTER: + dump.append(INDENT4 "DeviceType: pointer\n"); + break; default: assert(false); } @@ -1776,6 +1868,11 @@ bool TouchInputMapper::configureSurfaceLocked() { } } + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER + && mPointerController == NULL) { + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + } + bool orientationChanged = mLocked.surfaceOrientation != orientation; if (orientationChanged) { mLocked.surfaceOrientation = orientation; @@ -1997,6 +2094,37 @@ bool TouchInputMapper::configureSurfaceLocked() { mLocked.orientedRanges.y.fuzz = mLocked.yScale; break; } + + // Compute pointer gesture detection parameters. + // TODO: These factors should not be hardcoded. + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + int32_t rawWidth = mRawAxes.x.maxValue - mRawAxes.x.minValue + 1; + int32_t rawHeight = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1; + + // Scale movements such that one whole swipe of the touch pad covers a portion + // of the display along whichever axis of the touch pad is longer. + // Assume that the touch pad has a square aspect ratio such that movements in + // X and Y of the same number of raw units cover the same physical distance. + const float scaleFactor = 0.8f; + + mLocked.pointerGestureXMovementScale = rawWidth > rawHeight + ? scaleFactor * float(mLocked.associatedDisplayWidth) / rawWidth + : scaleFactor * float(mLocked.associatedDisplayHeight) / rawHeight; + mLocked.pointerGestureYMovementScale = mLocked.pointerGestureXMovementScale; + + // Scale zooms to cover a smaller range of the display than movements do. + // This value determines the area around the pointer that is affected by freeform + // pointer gestures. + mLocked.pointerGestureXZoomScale = mLocked.pointerGestureXMovementScale * 0.4f; + mLocked.pointerGestureYZoomScale = mLocked.pointerGestureYMovementScale * 0.4f; + + // Max width between pointers to detect a swipe gesture is 3/4 of the short + // axis of the touch pad. Touches that are wider than this are translated + // into freeform gestures. + mLocked.pointerGestureMaxSwipeWidthSquared = min(rawWidth, rawHeight) * 3 / 4; + mLocked.pointerGestureMaxSwipeWidthSquared *= + mLocked.pointerGestureMaxSwipeWidthSquared; + } } return true; @@ -2476,12 +2604,17 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { TouchResult touchResult = consumeOffScreenTouches(when, policyFlags); if (touchResult == DISPATCH_TOUCH) { suppressSwipeOntoVirtualKeys(when); + if (mPointerController != NULL) { + dispatchPointerGestures(when, policyFlags); + } dispatchTouches(when, policyFlags); } // Copy current touch to last touch in preparation for the next cycle. + // Keep the button state so we can track edge-triggered button state changes. if (touchResult == DROP_STROKE) { mLastTouch.clear(); + mLastTouch.buttonState = savedTouch->buttonState; } else { mLastTouch.copyFrom(*savedTouch); } @@ -2629,329 +2762,1097 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) { return; // nothing to do! } + // Update current touch coordinates. + int32_t edgeFlags; + float xPrecision, yPrecision; + prepareTouches(&edgeFlags, &xPrecision, &yPrecision); + + // Dispatch motions. BitSet32 currentIdBits = mCurrentTouch.idBits; BitSet32 lastIdBits = mLastTouch.idBits; + uint32_t metaState = getContext()->getGlobalMetaState(); if (currentIdBits == lastIdBits) { // No pointer id changes so this is a move event. // The dispatcher takes care of batching moves so we don't have to deal with that here. - int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE; - dispatchTouch(when, policyFlags, & mCurrentTouch, - currentIdBits, -1, currentPointerCount, motionEventAction); + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentTouchCoords, mCurrentTouch.idToIndex, currentIdBits, -1, + xPrecision, yPrecision, mDownTime); } else { // There may be pointers going up and pointers going down and pointers moving // all at the same time. - BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value); - BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value); - BitSet32 activeIdBits(lastIdBits.value); - uint32_t pointerCount = lastPointerCount; - - // Produce an intermediate representation of the touch data that consists of the - // old location of pointers that have just gone up and the new location of pointers that - // have just moved but omits the location of pointers that have just gone down. - TouchData interimTouch; - interimTouch.copyFrom(mLastTouch); - + BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value); + BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value); BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value); - bool moveNeeded = false; - while (!moveIdBits.isEmpty()) { - uint32_t moveId = moveIdBits.firstMarkedBit(); - moveIdBits.clearBit(moveId); - - int32_t oldIndex = mLastTouch.idToIndex[moveId]; - int32_t newIndex = mCurrentTouch.idToIndex[moveId]; - if (mLastTouch.pointers[oldIndex] != mCurrentTouch.pointers[newIndex]) { - interimTouch.pointers[oldIndex] = mCurrentTouch.pointers[newIndex]; - moveNeeded = true; - } - } + BitSet32 dispatchedIdBits(lastIdBits.value); + + // Update last coordinates of pointers that have moved so that we observe the new + // pointer positions at the same time as other pointers that have just gone up. + bool moveNeeded = updateMovedPointerCoords( + mCurrentTouchCoords, mCurrentTouch.idToIndex, + mLastTouchCoords, mLastTouch.idToIndex, + moveIdBits); - // Dispatch pointer up events using the interim pointer locations. + // Dispatch pointer up events. while (!upIdBits.isEmpty()) { uint32_t upId = upIdBits.firstMarkedBit(); upIdBits.clearBit(upId); - BitSet32 oldActiveIdBits = activeIdBits; - activeIdBits.clearBit(upId); - int32_t motionEventAction; - if (activeIdBits.isEmpty()) { - motionEventAction = AMOTION_EVENT_ACTION_UP; - } else { - motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP; - } - - dispatchTouch(when, policyFlags, &interimTouch, - oldActiveIdBits, upId, pointerCount, motionEventAction); - pointerCount -= 1; + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, metaState, 0, + mLastTouchCoords, mLastTouch.idToIndex, dispatchedIdBits, upId, + xPrecision, yPrecision, mDownTime); + dispatchedIdBits.clearBit(upId); } // Dispatch move events if any of the remaining pointers moved from their old locations. // Although applications receive new locations as part of individual pointer up // events, they do not generally handle them except when presented in a move event. if (moveNeeded) { - dispatchTouch(when, policyFlags, &mCurrentTouch, - activeIdBits, -1, pointerCount, AMOTION_EVENT_ACTION_MOVE); + assert(moveIdBits.value == dispatchedIdBits.value); + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, 0, + mCurrentTouchCoords, mCurrentTouch.idToIndex, dispatchedIdBits, -1, + xPrecision, yPrecision, mDownTime); } // Dispatch pointer down events using the new pointer locations. while (!downIdBits.isEmpty()) { uint32_t downId = downIdBits.firstMarkedBit(); downIdBits.clearBit(downId); - BitSet32 oldActiveIdBits = activeIdBits; - activeIdBits.markBit(downId); + dispatchedIdBits.markBit(downId); - int32_t motionEventAction; - if (oldActiveIdBits.isEmpty()) { - motionEventAction = AMOTION_EVENT_ACTION_DOWN; + if (dispatchedIdBits.count() == 1) { + // First pointer is going down. Set down time. mDownTime = when; } else { - motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN; + // Only send edge flags with first pointer down. + edgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; } - pointerCount += 1; - dispatchTouch(when, policyFlags, &mCurrentTouch, - activeIdBits, downId, pointerCount, motionEventAction); + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, edgeFlags, + mCurrentTouchCoords, mCurrentTouch.idToIndex, dispatchedIdBits, downId, + xPrecision, yPrecision, mDownTime); } } -} -void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags, - TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount, - int32_t motionEventAction) { - int32_t pointerIds[MAX_POINTERS]; - PointerCoords pointerCoords[MAX_POINTERS]; - int32_t motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; - float xPrecision, yPrecision; + // Update state for next time. + for (uint32_t i = 0; i < currentPointerCount; i++) { + mLastTouchCoords[i].copyFrom(mCurrentTouchCoords[i]); + } +} - { // acquire lock - AutoMutex _l(mLock); +void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags, + float* outXPrecision, float* outYPrecision) { + uint32_t currentPointerCount = mCurrentTouch.pointerCount; + uint32_t lastPointerCount = mLastTouch.pointerCount; - // Walk through the the active pointers and map touch screen coordinates (TouchData) into - // display or surface coordinates (PointerCoords) and adjust for display orientation. - for (uint32_t outIndex = 0; ! idBits.isEmpty(); outIndex++) { - uint32_t id = idBits.firstMarkedBit(); - idBits.clearBit(id); - uint32_t inIndex = touch->idToIndex[id]; + AutoMutex _l(mLock); - const PointerData& in = touch->pointers[inIndex]; + // Walk through the the active pointers and map touch screen coordinates (TouchData) into + // display or surface coordinates (PointerCoords) and adjust for display orientation. + for (uint32_t i = 0; i < currentPointerCount; i++) { + const PointerData& in = mCurrentTouch.pointers[i]; - // ToolMajor and ToolMinor - float toolMajor, toolMinor; - switch (mCalibration.toolSizeCalibration) { - case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC: - toolMajor = in.toolMajor * mLocked.geometricScale; - if (mRawAxes.toolMinor.valid) { - toolMinor = in.toolMinor * mLocked.geometricScale; - } else { - toolMinor = toolMajor; - } - break; - case Calibration::TOOL_SIZE_CALIBRATION_LINEAR: - toolMajor = in.toolMajor != 0 - ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias + // ToolMajor and ToolMinor + float toolMajor, toolMinor; + switch (mCalibration.toolSizeCalibration) { + case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC: + toolMajor = in.toolMajor * mLocked.geometricScale; + if (mRawAxes.toolMinor.valid) { + toolMinor = in.toolMinor * mLocked.geometricScale; + } else { + toolMinor = toolMajor; + } + break; + case Calibration::TOOL_SIZE_CALIBRATION_LINEAR: + toolMajor = in.toolMajor != 0 + ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias + : 0; + if (mRawAxes.toolMinor.valid) { + toolMinor = in.toolMinor != 0 + ? in.toolMinor * mLocked.toolSizeLinearScale + + mLocked.toolSizeLinearBias : 0; - if (mRawAxes.toolMinor.valid) { - toolMinor = in.toolMinor != 0 - ? in.toolMinor * mLocked.toolSizeLinearScale - + mLocked.toolSizeLinearBias - : 0; - } else { - toolMinor = toolMajor; - } - break; - case Calibration::TOOL_SIZE_CALIBRATION_AREA: - if (in.toolMajor != 0) { - float diameter = sqrtf(in.toolMajor - * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias); - toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias; - } else { - toolMajor = 0; - } + } else { toolMinor = toolMajor; - break; - default: + } + break; + case Calibration::TOOL_SIZE_CALIBRATION_AREA: + if (in.toolMajor != 0) { + float diameter = sqrtf(in.toolMajor + * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias); + toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias; + } else { toolMajor = 0; - toolMinor = 0; - break; } + toolMinor = toolMajor; + break; + default: + toolMajor = 0; + toolMinor = 0; + break; + } + + if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) { + toolMajor /= currentPointerCount; + toolMinor /= currentPointerCount; + } - if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) { - toolMajor /= pointerCount; - toolMinor /= pointerCount; + // Pressure + float rawPressure; + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_PRESSURE: + rawPressure = in.pressure; + break; + case Calibration::PRESSURE_SOURCE_TOUCH: + rawPressure = in.touchMajor; + break; + default: + rawPressure = 0; + } + + float pressure; + switch (mCalibration.pressureCalibration) { + case Calibration::PRESSURE_CALIBRATION_PHYSICAL: + case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: + pressure = rawPressure * mLocked.pressureScale; + break; + default: + pressure = 1; + break; + } + + // TouchMajor and TouchMinor + float touchMajor, touchMinor; + switch (mCalibration.touchSizeCalibration) { + case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC: + touchMajor = in.touchMajor * mLocked.geometricScale; + if (mRawAxes.touchMinor.valid) { + touchMinor = in.touchMinor * mLocked.geometricScale; + } else { + touchMinor = touchMajor; } + break; + case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: + touchMajor = toolMajor * pressure; + touchMinor = toolMinor * pressure; + break; + default: + touchMajor = 0; + touchMinor = 0; + break; + } - // Pressure - float rawPressure; - switch (mCalibration.pressureSource) { - case Calibration::PRESSURE_SOURCE_PRESSURE: - rawPressure = in.pressure; - break; - case Calibration::PRESSURE_SOURCE_TOUCH: - rawPressure = in.touchMajor; - break; - default: - rawPressure = 0; + if (touchMajor > toolMajor) { + touchMajor = toolMajor; + } + if (touchMinor > toolMinor) { + touchMinor = toolMinor; + } + + // Size + float size; + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_NORMALIZED: { + float rawSize = mRawAxes.toolMinor.valid + ? avg(in.toolMajor, in.toolMinor) + : in.toolMajor; + size = rawSize * mLocked.sizeScale; + break; + } + default: + size = 0; + break; + } + + // Orientation + float orientation; + switch (mCalibration.orientationCalibration) { + case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: + orientation = in.orientation * mLocked.orientationScale; + break; + case Calibration::ORIENTATION_CALIBRATION_VECTOR: { + int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); + int32_t c2 = signExtendNybble(in.orientation & 0x0f); + if (c1 != 0 || c2 != 0) { + orientation = atan2f(c1, c2) * 0.5f; + float scale = 1.0f + pythag(c1, c2) / 16.0f; + touchMajor *= scale; + touchMinor /= scale; + toolMajor *= scale; + toolMinor /= scale; + } else { + orientation = 0; } + break; + } + default: + orientation = 0; + } - float pressure; - switch (mCalibration.pressureCalibration) { - case Calibration::PRESSURE_CALIBRATION_PHYSICAL: - case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: - pressure = rawPressure * mLocked.pressureScale; - break; - default: - pressure = 1; - break; + // X and Y + // Adjust coords for surface orientation. + float x, y; + switch (mLocked.surfaceOrientation) { + case DISPLAY_ORIENTATION_90: + x = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; + y = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; + orientation -= M_PI_2; + if (orientation < - M_PI_2) { + orientation += M_PI; } + break; + case DISPLAY_ORIENTATION_180: + x = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; + y = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; + break; + case DISPLAY_ORIENTATION_270: + x = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; + y = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; + orientation += M_PI_2; + if (orientation > M_PI_2) { + orientation -= M_PI; + } + break; + default: + x = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; + y = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; + break; + } - // TouchMajor and TouchMinor - float touchMajor, touchMinor; - switch (mCalibration.touchSizeCalibration) { - case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC: - touchMajor = in.touchMajor * mLocked.geometricScale; - if (mRawAxes.touchMinor.valid) { - touchMinor = in.touchMinor * mLocked.geometricScale; - } else { - touchMinor = touchMajor; - } - break; - case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: - touchMajor = toolMajor * pressure; - touchMinor = toolMinor * pressure; - break; - default: - touchMajor = 0; - touchMinor = 0; - break; + // Write output coords. + PointerCoords& out = mCurrentTouchCoords[i]; + out.clear(); + out.setAxisValue(AMOTION_EVENT_AXIS_X, x); + out.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); + } + + // Check edge flags by looking only at the first pointer since the flags are + // global to the event. + *outEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + if (lastPointerCount == 0 && currentPointerCount > 0) { + const PointerData& in = mCurrentTouch.pointers[0]; + + if (in.x <= mRawAxes.x.minValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_LEFT, + mLocked.surfaceOrientation); + } else if (in.x >= mRawAxes.x.maxValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_RIGHT, + mLocked.surfaceOrientation); + } + if (in.y <= mRawAxes.y.minValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_TOP, + mLocked.surfaceOrientation); + } else if (in.y >= mRawAxes.y.maxValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_BOTTOM, + mLocked.surfaceOrientation); + } + } + + *outXPrecision = mLocked.orientedXPrecision; + *outYPrecision = mLocked.orientedYPrecision; +} + +void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags) { + // Update current gesture coordinates. + bool cancelPreviousGesture, finishPreviousGesture; + preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture); + + // Send events! + uint32_t metaState = getContext()->getGlobalMetaState(); + + // Update last coordinates of pointers that have moved so that we observe the new + // pointer positions at the same time as other pointers that have just gone up. + bool down = mPointerGesture.currentGestureMode == PointerGesture::CLICK_OR_DRAG + || mPointerGesture.currentGestureMode == PointerGesture::SWIPE + || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM; + bool moveNeeded = false; + if (down && !cancelPreviousGesture && !finishPreviousGesture + && mPointerGesture.lastGesturePointerCount != 0 + && mPointerGesture.currentGesturePointerCount != 0) { + BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value + & mPointerGesture.lastGestureIdBits.value); + moveNeeded = updateMovedPointerCoords( + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + movedGestureIdBits); + } + + // Send motion events for all pointers that went up or were canceled. + BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits); + if (!dispatchedGestureIdBits.isEmpty()) { + if (cancelPreviousGesture) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_CANCEL, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clear(); + } else { + BitSet32 upGestureIdBits; + if (finishPreviousGesture) { + upGestureIdBits = dispatchedGestureIdBits; + } else { + upGestureIdBits.value = dispatchedGestureIdBits.value + & ~mPointerGesture.currentGestureIdBits.value; + } + while (!upGestureIdBits.isEmpty()) { + uint32_t id = upGestureIdBits.firstMarkedBit(); + upGestureIdBits.clearBit(id); + + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, + metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clearBit(id); } + } + } - if (touchMajor > toolMajor) { - touchMajor = toolMajor; + // Send motion events for all pointers that moved. + if (moveNeeded) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Send motion events for all pointers that went down. + if (down) { + BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value + & ~dispatchedGestureIdBits.value); + while (!downGestureIdBits.isEmpty()) { + uint32_t id = downGestureIdBits.firstMarkedBit(); + downGestureIdBits.clearBit(id); + dispatchedGestureIdBits.markBit(id); + + int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + if (dispatchedGestureIdBits.count() == 1) { + // First pointer is going down. Calculate edge flags and set down time. + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerCoords& downCoords = mPointerGesture.currentGestureCoords[index]; + edgeFlags = calculateEdgeFlagsUsingPointerBounds(mPointerController, + downCoords.getAxisValue(AMOTION_EVENT_AXIS_X), + downCoords.getAxisValue(AMOTION_EVENT_AXIS_Y)); + mPointerGesture.downTime = when; } - if (touchMinor > toolMinor) { - touchMinor = toolMinor; + + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, edgeFlags, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + } + } + + // Send down and up for a tap. + if (mPointerGesture.currentGestureMode == PointerGesture::TAP) { + const PointerCoords& coords = mPointerGesture.currentGestureCoords[0]; + int32_t edgeFlags = calculateEdgeFlagsUsingPointerBounds(mPointerController, + coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y)); + nsecs_t downTime = mPointerGesture.downTime = mPointerGesture.tapTime; + mPointerGesture.resetTapTime(); + + dispatchMotion(downTime, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_DOWN, 0, metaState, edgeFlags, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, + 0, 0, downTime); + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_UP, 0, metaState, edgeFlags, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, + 0, 0, downTime); + } + + // Send motion events for hover. + if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Update state. + mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode; + if (!down) { + mPointerGesture.lastGesturePointerCount = 0; + mPointerGesture.lastGestureIdBits.clear(); + } else { + uint32_t currentGesturePointerCount = mPointerGesture.currentGesturePointerCount; + mPointerGesture.lastGesturePointerCount = currentGesturePointerCount; + mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits; + for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + mPointerGesture.lastGestureCoords[index].copyFrom( + mPointerGesture.currentGestureCoords[index]); + mPointerGesture.lastGestureIdToIndex[id] = index; + } + } +} + +void TouchInputMapper::preparePointerGestures(nsecs_t when, + bool* outCancelPreviousGesture, bool* outFinishPreviousGesture) { + *outCancelPreviousGesture = false; + *outFinishPreviousGesture = false; + + AutoMutex _l(mLock); + + // Update the velocity tracker. + { + VelocityTracker::Position positions[MAX_POINTERS]; + uint32_t count = 0; + for (BitSet32 idBits(mCurrentTouch.idBits); !idBits.isEmpty(); count++) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mCurrentTouch.idToIndex[id]; + positions[count].x = mCurrentTouch.pointers[index].x + * mLocked.pointerGestureXMovementScale; + positions[count].y = mCurrentTouch.pointers[index].y + * mLocked.pointerGestureYMovementScale; + } + mPointerGesture.velocityTracker.addMovement(when, mCurrentTouch.idBits, positions); + } + + // Pick a new active touch id if needed. + // Choose an arbitrary pointer that just went down, if there is one. + // Otherwise choose an arbitrary remaining pointer. + // This guarantees we always have an active touch id when there is at least one pointer. + // We always switch to the newest pointer down because that's usually where the user's + // attention is focused. + int32_t activeTouchId; + BitSet32 downTouchIdBits(mCurrentTouch.idBits.value & ~mLastTouch.idBits.value); + if (!downTouchIdBits.isEmpty()) { + activeTouchId = mPointerGesture.activeTouchId = downTouchIdBits.firstMarkedBit(); + } else { + activeTouchId = mPointerGesture.activeTouchId; + if (activeTouchId < 0 || !mCurrentTouch.idBits.hasBit(activeTouchId)) { + if (!mCurrentTouch.idBits.isEmpty()) { + activeTouchId = mPointerGesture.activeTouchId = + mCurrentTouch.idBits.firstMarkedBit(); + } else { + activeTouchId = mPointerGesture.activeTouchId = -1; } + } + } - // Size - float size; - switch (mCalibration.sizeCalibration) { - case Calibration::SIZE_CALIBRATION_NORMALIZED: { - float rawSize = mRawAxes.toolMinor.valid - ? avg(in.toolMajor, in.toolMinor) - : in.toolMajor; - size = rawSize * mLocked.sizeScale; - break; + // Update the touch origin data to track where each finger originally went down. + if (mCurrentTouch.pointerCount == 0 || mPointerGesture.touchOrigin.pointerCount == 0) { + // Fast path when all fingers have gone up or down. + mPointerGesture.touchOrigin.copyFrom(mCurrentTouch); + } else { + // Slow path when only some fingers have gone up or down. + for (BitSet32 idBits(mPointerGesture.touchOrigin.idBits.value + & ~mCurrentTouch.idBits.value); !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + mPointerGesture.touchOrigin.idBits.clearBit(id); + uint32_t index = mPointerGesture.touchOrigin.idToIndex[id]; + uint32_t count = --mPointerGesture.touchOrigin.pointerCount; + while (index < count) { + mPointerGesture.touchOrigin.pointers[index] = + mPointerGesture.touchOrigin.pointers[index + 1]; + uint32_t movedId = mPointerGesture.touchOrigin.pointers[index].id; + mPointerGesture.touchOrigin.idToIndex[movedId] = index; + index += 1; } - default: - size = 0; - break; + } + for (BitSet32 idBits(mCurrentTouch.idBits.value + & ~mPointerGesture.touchOrigin.idBits.value); !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + mPointerGesture.touchOrigin.idBits.markBit(id); + uint32_t index = mPointerGesture.touchOrigin.pointerCount++; + mPointerGesture.touchOrigin.pointers[index] = + mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]]; + mPointerGesture.touchOrigin.idToIndex[id] = index; + } + } + + // Determine whether we are in quiet time. + bool isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL; + if (!isQuietTime) { + if ((mPointerGesture.lastGestureMode == PointerGesture::SWIPE + || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM) + && mCurrentTouch.pointerCount < 2) { + // Enter quiet time when exiting swipe or freeform state. + // This is to prevent accidentally entering the hover state and flinging the + // pointer when finishing a swipe and there is still one pointer left onscreen. + isQuietTime = true; + } else if (mPointerGesture.lastGestureMode == PointerGesture::CLICK_OR_DRAG + && mCurrentTouch.pointerCount >= 2 + && !isPointerDown(mCurrentTouch.buttonState)) { + // Enter quiet time when releasing the button and there are still two or more + // fingers down. This may indicate that one finger was used to press the button + // but it has not gone up yet. + isQuietTime = true; + } + if (isQuietTime) { + mPointerGesture.quietTime = when; + } + } + + // Switch states based on button and pointer state. + if (isQuietTime) { + // Case 1: Quiet time. (QUIET) +#if DEBUG_GESTURES + LOGD("Gestures: QUIET for next %0.3fms", + (mPointerGesture.quietTime + QUIET_INTERVAL - when) * 0.000001f); +#endif + *outFinishPreviousGesture = true; + + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::QUIET; + mPointerGesture.currentGesturePointerCount = 0; + mPointerGesture.currentGestureIdBits.clear(); + } else if (isPointerDown(mCurrentTouch.buttonState)) { + // Case 2: Button is pressed. (DRAG) + // The pointer follows the active touch point. + // Emit DOWN, MOVE, UP events at the pointer location. + // + // Only the active touch matters; other fingers are ignored. This policy helps + // to handle the case where the user places a second finger on the touch pad + // to apply the necessary force to depress an integrated button below the surface. + // We don't want the second finger to be delivered to applications. + // + // For this to work well, we need to make sure to track the pointer that is really + // active. If the user first puts one finger down to click then adds another + // finger to drag then the active pointer should switch to the finger that is + // being dragged. +#if DEBUG_GESTURES + LOGD("Gestures: CLICK_OR_DRAG activeTouchId=%d, " + "currentTouchPointerCount=%d", activeTouchId, mCurrentTouch.pointerCount); +#endif + // Reset state when just starting. + if (mPointerGesture.lastGestureMode != PointerGesture::CLICK_OR_DRAG) { + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = 0; + } + + // Switch pointers if needed. + // Find the fastest pointer and follow it. + if (activeTouchId >= 0) { + if (mCurrentTouch.pointerCount > 1) { + int32_t bestId = -1; + float bestSpeed = DRAG_MIN_SWITCH_SPEED; + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t id = mCurrentTouch.pointers[i].id; + float vx, vy; + if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { + float speed = pythag(vx, vy); + if (speed > bestSpeed) { + bestId = id; + bestSpeed = speed; + } + } + } + if (bestId >= 0 && bestId != activeTouchId) { + mPointerGesture.activeTouchId = activeTouchId = bestId; +#if DEBUG_GESTURES + LOGD("Gestures: CLICK_OR_DRAG switched pointers, " + "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed); +#endif + } } - // Orientation - float orientation; - switch (mCalibration.orientationCalibration) { - case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: - orientation = in.orientation * mLocked.orientationScale; - break; - case Calibration::ORIENTATION_CALIBRATION_VECTOR: { - int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); - int32_t c2 = signExtendNybble(in.orientation & 0x0f); - if (c1 != 0 || c2 != 0) { - orientation = atan2f(c1, c2) * 0.5f; - float scale = 1.0f + pythag(c1, c2) / 16.0f; - touchMajor *= scale; - touchMinor /= scale; - toolMajor *= scale; - toolMinor /= scale; + if (mLastTouch.idBits.hasBit(activeTouchId)) { + const PointerData& currentPointer = + mCurrentTouch.pointers[mCurrentTouch.idToIndex[activeTouchId]]; + const PointerData& lastPointer = + mLastTouch.pointers[mLastTouch.idToIndex[activeTouchId]]; + float deltaX = (currentPointer.x - lastPointer.x) + * mLocked.pointerGestureXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) + * mLocked.pointerGestureYMovementScale; + mPointerController->move(deltaX, deltaY); + } + } + + float x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureMode = PointerGesture::CLICK_OR_DRAG; + mPointerGesture.currentGesturePointerCount = 1; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + } else if (mCurrentTouch.pointerCount == 0) { + // Case 3. No fingers down and button is not pressed. (NEUTRAL) + *outFinishPreviousGesture = true; + + // Watch for taps coming out of HOVER or INDETERMINATE_MULTITOUCH mode. + bool tapped = false; + if (mPointerGesture.lastGestureMode == PointerGesture::HOVER + || mPointerGesture.lastGestureMode + == PointerGesture::INDETERMINATE_MULTITOUCH) { + if (when <= mPointerGesture.tapTime + TAP_INTERVAL) { + float x, y; + mPointerController->getPosition(&x, &y); + if (fabs(x - mPointerGesture.initialPointerX) <= TAP_SLOP + && fabs(y - mPointerGesture.initialPointerY) <= TAP_SLOP) { +#if DEBUG_GESTURES + LOGD("Gestures: TAP"); +#endif + mPointerGesture.activeGestureId = 0; + mPointerGesture.currentGestureMode = PointerGesture::TAP; + mPointerGesture.currentGesturePointerCount = 1; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit( + mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[ + mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_X, mPointerGesture.initialPointerX); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_Y, mPointerGesture.initialPointerY); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + tapped = true; } else { - orientation = 0; +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", + x - mPointerGesture.initialPointerX, + y - mPointerGesture.initialPointerY); +#endif } - break; + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP, delay=%lld", + when - mPointerGesture.tapTime); +#endif } - default: - orientation = 0; + } + if (!tapped) { +#if DEBUG_GESTURES + LOGD("Gestures: NEUTRAL"); +#endif + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; + mPointerGesture.currentGesturePointerCount = 0; + mPointerGesture.currentGestureIdBits.clear(); + } + } else if (mCurrentTouch.pointerCount == 1) { + // Case 4. Exactly one finger down, button is not pressed. (HOVER) + // The pointer follows the active touch point. + // Emit HOVER_MOVE events at the pointer location. + assert(activeTouchId >= 0); + +#if DEBUG_GESTURES + LOGD("Gestures: HOVER"); +#endif + + if (mLastTouch.idBits.hasBit(activeTouchId)) { + const PointerData& currentPointer = + mCurrentTouch.pointers[mCurrentTouch.idToIndex[activeTouchId]]; + const PointerData& lastPointer = + mLastTouch.pointers[mLastTouch.idToIndex[activeTouchId]]; + float deltaX = (currentPointer.x - lastPointer.x) + * mLocked.pointerGestureXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) + * mLocked.pointerGestureYMovementScale; + mPointerController->move(deltaX, deltaY); + } + + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = 0; + + float x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureMode = PointerGesture::HOVER; + mPointerGesture.currentGesturePointerCount = 1; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); + + if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) { + mPointerGesture.tapTime = when; + mPointerGesture.initialPointerX = x; + mPointerGesture.initialPointerY = y; + } + } else { + // Case 5. At least two fingers down, button is not pressed. (SWIPE or FREEFORM + // or INDETERMINATE_MULTITOUCH) + // Initially we watch and wait for something interesting to happen so as to + // avoid making a spurious guess as to the nature of the gesture. For example, + // the fingers may be in transition to some other state such as pressing or + // releasing the button or we may be performing a two finger tap. + // + // Fix the centroid of the figure when the gesture actually starts. + // We do not recalculate the centroid at any other time during the gesture because + // it would affect the relationship of the touch points relative to the pointer location. + assert(activeTouchId >= 0); + + uint32_t currentTouchPointerCount = mCurrentTouch.pointerCount; + if (currentTouchPointerCount > MAX_POINTERS) { + currentTouchPointerCount = MAX_POINTERS; + } + + if (mPointerGesture.lastGestureMode != PointerGesture::INDETERMINATE_MULTITOUCH + && mPointerGesture.lastGestureMode != PointerGesture::SWIPE + && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + mPointerGesture.currentGestureMode = PointerGesture::INDETERMINATE_MULTITOUCH; + + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = -1; + + // Remember the initial pointer location. + // Everything we do will be relative to this location. + mPointerController->getPosition(&mPointerGesture.initialPointerX, + &mPointerGesture.initialPointerY); + + // Track taps. + if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) { + mPointerGesture.tapTime = when; } - // X and Y - // Adjust coords for surface orientation. - float x, y; - switch (mLocked.surfaceOrientation) { - case DISPLAY_ORIENTATION_90: - x = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; - y = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; - orientation -= M_PI_2; - if (orientation < - M_PI_2) { - orientation += M_PI; - } - break; - case DISPLAY_ORIENTATION_180: - x = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; - y = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; - break; - case DISPLAY_ORIENTATION_270: - x = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; - y = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; - orientation += M_PI_2; - if (orientation > M_PI_2) { - orientation -= M_PI; + // Reset the touch origin to be relative to exactly where the fingers are now + // in case they have moved some distance away as part of a previous gesture. + // We want to know how far the fingers have traveled since we started considering + // a multitouch gesture. + mPointerGesture.touchOrigin.copyFrom(mCurrentTouch); + } else { + mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + } + + if (mPointerGesture.currentGestureMode == PointerGesture::INDETERMINATE_MULTITOUCH) { + // Wait for the pointers to start moving before doing anything. + bool decideNow = true; + for (uint32_t i = 0; i < currentTouchPointerCount; i++) { + const PointerData& current = mCurrentTouch.pointers[i]; + const PointerData& origin = mPointerGesture.touchOrigin.pointers[ + mPointerGesture.touchOrigin.idToIndex[current.id]]; + float distance = pythag( + (current.x - origin.x) * mLocked.pointerGestureXZoomScale, + (current.y - origin.y) * mLocked.pointerGestureYZoomScale); + if (distance < MULTITOUCH_MIN_TRAVEL) { + decideNow = false; + break; } - break; - default: - x = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; - y = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; - break; } - // Write output coords. - PointerCoords& out = pointerCoords[outIndex]; - out.clear(); - out.setAxisValue(AMOTION_EVENT_AXIS_X, x); - out.setAxisValue(AMOTION_EVENT_AXIS_Y, y); - out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); - out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); - out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); - out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); - - pointerIds[outIndex] = int32_t(id); - - if (id == changedId) { - motionEventAction |= outIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + if (decideNow) { + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + if (currentTouchPointerCount == 2 + && distanceSquared( + mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y, + mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y) + <= mLocked.pointerGestureMaxSwipeWidthSquared) { + const PointerData& current1 = mCurrentTouch.pointers[0]; + const PointerData& current2 = mCurrentTouch.pointers[1]; + const PointerData& origin1 = mPointerGesture.touchOrigin.pointers[ + mPointerGesture.touchOrigin.idToIndex[current1.id]]; + const PointerData& origin2 = mPointerGesture.touchOrigin.pointers[ + mPointerGesture.touchOrigin.idToIndex[current2.id]]; + + float x1 = (current1.x - origin1.x) * mLocked.pointerGestureXZoomScale; + float y1 = (current1.y - origin1.y) * mLocked.pointerGestureYZoomScale; + float x2 = (current2.x - origin2.x) * mLocked.pointerGestureXZoomScale; + float y2 = (current2.y - origin2.y) * mLocked.pointerGestureYZoomScale; + float magnitude1 = pythag(x1, y1); + float magnitude2 = pythag(x2, y2); + + // Calculate the dot product of the vectors. + // When the vectors are oriented in approximately the same direction, + // the angle betweeen them is near zero and the cosine of the angle + // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2). + // We know that the magnitude is at least MULTITOUCH_MIN_TRAVEL because + // we checked it above. + float dot = x1 * x2 + y1 * y2; + float cosine = dot / (magnitude1 * magnitude2); // denominator always > 0 + if (cosine > SWIPE_TRANSITION_ANGLE_COSINE) { + mPointerGesture.currentGestureMode = PointerGesture::SWIPE; + } + } + + // Remember the initial centroid for the duration of the gesture. + mPointerGesture.initialCentroidX = 0; + mPointerGesture.initialCentroidY = 0; + for (uint32_t i = 0; i < currentTouchPointerCount; i++) { + const PointerData& touch = mCurrentTouch.pointers[i]; + mPointerGesture.initialCentroidX += touch.x; + mPointerGesture.initialCentroidY += touch.y; + } + mPointerGesture.initialCentroidX /= int32_t(currentTouchPointerCount); + mPointerGesture.initialCentroidY /= int32_t(currentTouchPointerCount); + + mPointerGesture.activeGestureId = 0; + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // Switch to FREEFORM if additional pointers go down. + if (currentTouchPointerCount > 2) { + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; } } - // Check edge flags by looking only at the first pointer since the flags are - // global to the event. - if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) { - uint32_t inIndex = touch->idToIndex[pointerIds[0]]; - const PointerData& in = touch->pointers[inIndex]; + if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // SWIPE mode. +#if DEBUG_GESTURES + LOGD("Gestures: SWIPE activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount); +#endif + assert(mPointerGesture.activeGestureId >= 0); + + float x = (mCurrentTouch.pointers[0].x + mCurrentTouch.pointers[1].x + - mPointerGesture.initialCentroidX * 2) * 0.5f + * mLocked.pointerGestureXMovementScale + mPointerGesture.initialPointerX; + float y = (mCurrentTouch.pointers[0].y + mCurrentTouch.pointers[1].y + - mPointerGesture.initialCentroidY * 2) * 0.5f + * mLocked.pointerGestureYMovementScale + mPointerGesture.initialPointerY; + + mPointerGesture.currentGesturePointerCount = 1; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) { + // FREEFORM mode. +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount); +#endif + assert(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGesturePointerCount = currentTouchPointerCount; + mPointerGesture.currentGestureIdBits.clear(); + + BitSet32 mappedTouchIdBits; + BitSet32 usedGestureIdBits; + if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + // Initially, assign the active gesture id to the active touch point + // if there is one. No other touch id bits are mapped yet. + if (!*outCancelPreviousGesture) { + mappedTouchIdBits.markBit(activeTouchId); + usedGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = + mPointerGesture.activeGestureId; + } else { + mPointerGesture.activeGestureId = -1; + } + } else { + // Otherwise, assume we mapped all touches from the previous frame. + // Reuse all mappings that are still applicable. + mappedTouchIdBits.value = mLastTouch.idBits.value & mCurrentTouch.idBits.value; + usedGestureIdBits = mPointerGesture.lastGestureIdBits; + + // Check whether we need to choose a new active gesture id because the + // current went went up. + for (BitSet32 upTouchIdBits(mLastTouch.idBits.value & ~mCurrentTouch.idBits.value); + !upTouchIdBits.isEmpty(); ) { + uint32_t upTouchId = upTouchIdBits.firstMarkedBit(); + upTouchIdBits.clearBit(upTouchId); + uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId]; + if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) { + mPointerGesture.activeGestureId = -1; + break; + } + } + } + +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM follow up " + "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " + "activeGestureId=%d", + mappedTouchIdBits.value, usedGestureIdBits.value, + mPointerGesture.activeGestureId); +#endif - if (in.x <= mRawAxes.x.minValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_LEFT, - mLocked.surfaceOrientation); - } else if (in.x >= mRawAxes.x.maxValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_RIGHT, - mLocked.surfaceOrientation); + for (uint32_t i = 0; i < currentTouchPointerCount; i++) { + uint32_t touchId = mCurrentTouch.pointers[i].id; + uint32_t gestureId; + if (!mappedTouchIdBits.hasBit(touchId)) { + gestureId = usedGestureIdBits.firstUnmarkedBit(); + usedGestureIdBits.markBit(gestureId); + mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM " + "new mapping for touch id %d -> gesture id %d", + touchId, gestureId); +#endif + } else { + gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM " + "existing mapping for touch id %d -> gesture id %d", + touchId, gestureId); +#endif + } + mPointerGesture.currentGestureIdBits.markBit(gestureId); + mPointerGesture.currentGestureIdToIndex[gestureId] = i; + + float x = (mCurrentTouch.pointers[i].x - mPointerGesture.initialCentroidX) + * mLocked.pointerGestureXZoomScale + mPointerGesture.initialPointerX; + float y = (mCurrentTouch.pointers[i].y - mPointerGesture.initialCentroidY) + * mLocked.pointerGestureYZoomScale + mPointerGesture.initialPointerY; + + mPointerGesture.currentGestureCoords[i].clear(); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_PRESSURE, 1.0f); } - if (in.y <= mRawAxes.y.minValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_TOP, - mLocked.surfaceOrientation); - } else if (in.y >= mRawAxes.y.maxValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_BOTTOM, - mLocked.surfaceOrientation); + + if (mPointerGesture.activeGestureId < 0) { + mPointerGesture.activeGestureId = + mPointerGesture.currentGestureIdBits.firstMarkedBit(); +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM new " + "activeGestureId=%d", mPointerGesture.activeGestureId); +#endif } + } else { + // INDETERMINATE_MULTITOUCH mode. + // Do nothing. +#if DEBUG_GESTURES + LOGD("Gestures: INDETERMINATE_MULTITOUCH"); +#endif } + } - xPrecision = mLocked.orientedXPrecision; - yPrecision = mLocked.orientedYPrecision; - } // release lock + // Unfade the pointer if the user is doing anything with the touch pad. + mPointerController->setButtonState(mCurrentTouch.buttonState); + if (mCurrentTouch.buttonState || mCurrentTouch.pointerCount != 0) { + mPointerController->unfade(); + } + +#if DEBUG_GESTURES + LOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " + "currentGestureMode=%d, currentGesturePointerCount=%d, currentGestureIdBits=0x%08x, " + "lastGestureMode=%d, lastGesturePointerCount=%d, lastGestureIdBits=0x%08x", + toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), + mPointerGesture.currentGestureMode, mPointerGesture.currentGesturePointerCount, + mPointerGesture.currentGestureIdBits.value, + mPointerGesture.lastGestureMode, mPointerGesture.lastGesturePointerCount, + mPointerGesture.lastGestureIdBits.value); + for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; + LOGD(" currentGesture[%d]: index=%d, x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } + for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; + const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; + LOGD(" lastGesture[%d]: index=%d, x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } +#endif +} + +void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, + int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags, + const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) { + PointerCoords pointerCoords[MAX_POINTERS]; + int32_t pointerIds[MAX_POINTERS]; + uint32_t pointerCount = 0; + while (!idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = idToIndex[id]; + pointerIds[pointerCount] = id; + pointerCoords[pointerCount].copyFrom(coords[index]); + + if (changedId >= 0 && id == uint32_t(changedId)) { + action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + } + + pointerCount += 1; + } + + assert(pointerCount != 0); + + if (changedId >= 0 && pointerCount == 1) { + // Replace initial down and final up action. + // We can compare the action without masking off the changed pointer index + // because we know the index is 0. + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) { + action = AMOTION_EVENT_ACTION_DOWN; + } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) { + action = AMOTION_EVENT_ACTION_UP; + } else { + // Can't happen. + assert(false); + } + } + + getDispatcher()->notifyMotion(when, getDeviceId(), source, policyFlags, + action, flags, metaState, edgeFlags, + pointerCount, pointerIds, pointerCoords, xPrecision, yPrecision, downTime); +} - getDispatcher()->notifyMotion(when, getDeviceId(), mTouchSource, policyFlags, - motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags, - pointerCount, pointerIds, pointerCoords, - xPrecision, yPrecision, mDownTime); +bool TouchInputMapper::updateMovedPointerCoords( + const PointerCoords* inCoords, const uint32_t* inIdToIndex, + PointerCoords* outCoords, const uint32_t* outIdToIndex, BitSet32 idBits) const { + bool changed = false; + while (!idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + + uint32_t inIndex = inIdToIndex[id]; + uint32_t outIndex = outIdToIndex[id]; + const PointerCoords& curInCoords = inCoords[inIndex]; + PointerCoords& curOutCoords = outCoords[outIndex]; + + if (curInCoords != curOutCoords) { + curOutCoords.copyFrom(curInCoords); + changed = true; + } + } + return changed; +} + +void TouchInputMapper::fadePointer() { + { // acquire lock + AutoMutex _l(mLock); + if (mPointerController != NULL) { + mPointerController->fade(); + } + } // release lock } bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) { @@ -3592,6 +4493,7 @@ void SingleTouchInputMapper::initialize() { mY = 0; mPressure = 0; // default to 0 for devices that don't report pressure mToolWidth = 0; // default to 0 for devices that don't report tool width + mButtonState = 0; } void SingleTouchInputMapper::reset() { @@ -3611,6 +4513,19 @@ void SingleTouchInputMapper::process(const RawEvent* rawEvent) { // not have received valid position information yet. This logic assumes that // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet. break; + default: + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + uint32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode); + if (buttonState) { + if (rawEvent->value) { + mAccumulator.buttonDown |= buttonState; + } else { + mAccumulator.buttonUp |= buttonState; + } + mAccumulator.fields |= Accumulator::FIELD_BUTTONS; + } + } + break; } break; @@ -3671,6 +4586,10 @@ void SingleTouchInputMapper::sync(nsecs_t when) { mToolWidth = mAccumulator.absToolWidth; } + if (fields & Accumulator::FIELD_BUTTONS) { + mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp; + } + mCurrentTouch.clear(); if (mDown) { @@ -3686,6 +4605,7 @@ void SingleTouchInputMapper::sync(nsecs_t when) { mCurrentTouch.pointers[0].orientation = 0; mCurrentTouch.idToIndex[0] = 0; mCurrentTouch.idBits.markBit(0); + mCurrentTouch.buttonState = mButtonState; } syncTouch(when, true); @@ -3715,6 +4635,7 @@ MultiTouchInputMapper::~MultiTouchInputMapper() { void MultiTouchInputMapper::initialize() { mAccumulator.clear(); + mButtonState = 0; } void MultiTouchInputMapper::reset() { @@ -3725,6 +4646,20 @@ void MultiTouchInputMapper::reset() { void MultiTouchInputMapper::process(const RawEvent* rawEvent) { switch (rawEvent->type) { + case EV_KEY: { + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + uint32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode); + if (buttonState) { + if (rawEvent->value) { + mAccumulator.buttonDown |= buttonState; + } else { + mAccumulator.buttonUp |= buttonState; + } + } + } + break; + } + case EV_ABS: { uint32_t pointerIndex = mAccumulator.pointerCount; Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex]; @@ -3902,6 +4837,9 @@ void MultiTouchInputMapper::sync(nsecs_t when) { mCurrentTouch.pointerCount = outCount; + mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp; + mCurrentTouch.buttonState = mButtonState; + syncTouch(when, havePointerIds); mAccumulator.clear(); diff --git a/services/input/InputReader.h b/services/input/InputReader.h index 68002ca..55ab479 100644 --- a/services/input/InputReader.h +++ b/services/input/InputReader.h @@ -558,6 +558,8 @@ public: virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags); + virtual void fadePointer(); + protected: Mutex mLock; @@ -611,10 +613,12 @@ protected: PointerData pointers[MAX_POINTERS]; BitSet32 idBits; uint32_t idToIndex[MAX_POINTER_ID + 1]; + uint32_t buttonState; void copyFrom(const TouchData& other) { pointerCount = other.pointerCount; idBits = other.idBits; + buttonState = other.buttonState; for (uint32_t i = 0; i < pointerCount; i++) { pointers[i] = other.pointers[i]; @@ -627,17 +631,20 @@ protected: inline void clear() { pointerCount = 0; idBits.clear(); + buttonState = 0; } }; // Input sources supported by the device. uint32_t mTouchSource; // sources when reporting touch data + uint32_t mPointerSource; // sources when reporting pointer gestures // Immutable configuration parameters. struct Parameters { enum DeviceType { DEVICE_TYPE_TOUCH_SCREEN, DEVICE_TYPE_TOUCH_PAD, + DEVICE_TYPE_POINTER, }; DeviceType deviceType; @@ -735,11 +742,17 @@ protected: // Current and previous touch sample data. TouchData mCurrentTouch; + PointerCoords mCurrentTouchCoords[MAX_POINTERS]; + TouchData mLastTouch; + PointerCoords mLastTouchCoords[MAX_POINTERS]; // The time the primary pointer last went down. nsecs_t mDownTime; + // The pointer controller, or null if the device is not a pointer. + sp<PointerControllerInterface> mPointerController; + struct LockedState { Vector<VirtualKey> virtualKeys; @@ -804,6 +817,17 @@ protected: int32_t keyCode; int32_t scanCode; } currentVirtualKey; + + // Scale factor for gesture based pointer movements. + float pointerGestureXMovementScale; + float pointerGestureYMovementScale; + + // Scale factor for gesture based zooming and other freeform motions. + float pointerGestureXZoomScale; + float pointerGestureYZoomScale; + + // The maximum swipe width squared. + int32_t pointerGestureMaxSwipeWidthSquared; } mLocked; virtual void configureParameters(); @@ -869,13 +893,148 @@ private: uint64_t distance : 48; // squared distance }; + struct PointerGesture { + enum Mode { + // No fingers, button is not pressed. + // Nothing happening. + NEUTRAL, + + // No fingers, button is not pressed. + // Tap detected. + // Emits DOWN and UP events at the pointer location. + TAP, + + // Button is pressed. + // Pointer follows the active finger if there is one. Other fingers are ignored. + // Emits DOWN, MOVE and UP events at the pointer location. + CLICK_OR_DRAG, + + // Exactly one finger, button is not pressed. + // Pointer follows the active finger. + // Emits HOVER_MOVE events at the pointer location. + HOVER, + + // More than two fingers involved but they haven't moved enough for us + // to figure out what is intended. + INDETERMINATE_MULTITOUCH, + + // Exactly two fingers moving in the same direction, button is not pressed. + // Pointer does not move. + // Emits DOWN, MOVE and UP events with a single pointer coordinate that + // follows the midpoint between both fingers. + // The centroid is fixed when entering this state. + SWIPE, + + // Two or more fingers moving in arbitrary directions, button is not pressed. + // Pointer does not move. + // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow + // each finger individually relative to the initial centroid of the finger. + // The centroid is fixed when entering this state. + FREEFORM, + + // Waiting for quiet time to end before starting the next gesture. + QUIET, + }; + + // The active pointer id from the raw touch data. + int32_t activeTouchId; // -1 if none + + // The active pointer id from the gesture last delivered to the application. + int32_t activeGestureId; // -1 if none + + // Pointer coords and ids for the current and previous pointer gesture. + Mode currentGestureMode; + uint32_t currentGesturePointerCount; + BitSet32 currentGestureIdBits; + uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1]; + PointerCoords currentGestureCoords[MAX_POINTERS]; + + Mode lastGestureMode; + uint32_t lastGesturePointerCount; + BitSet32 lastGestureIdBits; + uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1]; + PointerCoords lastGestureCoords[MAX_POINTERS]; + + // Tracks for all pointers originally went down. + TouchData touchOrigin; + + // Describes how touch ids are mapped to gesture ids for freeform gestures. + uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1]; + + // Initial centroid of the movement. + // Used to calculate how far the touch pointers have moved since the gesture started. + int32_t initialCentroidX; + int32_t initialCentroidY; + + // Initial pointer location. + // Used to track where the pointer was when the gesture started. + float initialPointerX; + float initialPointerY; + + // Time the pointer gesture last went down. + nsecs_t downTime; + + // Time we started waiting for a tap gesture. + nsecs_t tapTime; + + // Time we started waiting for quiescence. + nsecs_t quietTime; + + // A velocity tracker for determining whether to switch active pointers during drags. + VelocityTracker velocityTracker; + + void reset() { + activeTouchId = -1; + activeGestureId = -1; + currentGestureMode = NEUTRAL; + currentGesturePointerCount = 0; + currentGestureIdBits.clear(); + lastGestureMode = NEUTRAL; + lastGesturePointerCount = 0; + lastGestureIdBits.clear(); + touchOrigin.clear(); + initialCentroidX = 0; + initialCentroidY = 0; + initialPointerX = 0; + initialPointerY = 0; + downTime = 0; + velocityTracker.clear(); + resetTapTime(); + resetQuietTime(); + } + + void resetTapTime() { + tapTime = LLONG_MIN; + } + + void resetQuietTime() { + quietTime = LLONG_MIN; + } + } mPointerGesture; + void initializeLocked(); TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags); void dispatchTouches(nsecs_t when, uint32_t policyFlags); - void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch, - BitSet32 idBits, uint32_t changedId, uint32_t pointerCount, - int32_t motionEventAction); + void prepareTouches(int32_t* outEdgeFlags, float* outXPrecision, float* outYPrecision); + void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags); + void preparePointerGestures(nsecs_t when, + bool* outCancelPreviousGesture, bool* outFinishPreviousGesture); + + // Dispatches a motion event. + // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the + // method will take care of setting the index and transmuting the action to DOWN or UP + // it is the first / last pointer to go down / up. + void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, + int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags, + const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime); + + // Updates pointer coords for pointers with specified ids that have moved. + // Returns true if any of them changed. + bool updateMovedPointerCoords(const PointerCoords* inCoords, const uint32_t* inIdToIndex, + PointerCoords* outCoords, const uint32_t* outIdToIndex, BitSet32 idBits) const; + void suppressSwipeOntoVirtualKeys(nsecs_t when); bool isPointInsideSurfaceLocked(int32_t x, int32_t y); @@ -907,6 +1066,7 @@ private: FIELD_ABS_Y = 4, FIELD_ABS_PRESSURE = 8, FIELD_ABS_TOOL_WIDTH = 16, + FIELD_BUTTONS = 32, }; uint32_t fields; @@ -917,8 +1077,13 @@ private: int32_t absPressure; int32_t absToolWidth; + uint32_t buttonDown; + uint32_t buttonUp; + inline void clear() { fields = 0; + buttonDown = 0; + buttonUp = 0; } } mAccumulator; @@ -927,6 +1092,7 @@ private: int32_t mY; int32_t mPressure; int32_t mToolWidth; + uint32_t mButtonState; void initialize(); @@ -978,12 +1144,20 @@ private: } } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks + // Bitfield of buttons that went down or up. + uint32_t buttonDown; + uint32_t buttonUp; + inline void clear() { pointerCount = 0; pointers[0].clear(); + buttonDown = 0; + buttonUp = 0; } } mAccumulator; + uint32_t mButtonState; + void initialize(); void sync(nsecs_t when); diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp index 67a2e21..075eff3 100644 --- a/services/input/tests/InputReader_test.cpp +++ b/services/input/tests/InputReader_test.cpp @@ -2448,11 +2448,21 @@ void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) { } -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecified_ReturnsTouchPad) { +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) { SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); prepareAxes(POSITION); addMapperAndConfigure(mapper); + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_X); + mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_Y); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources()); } |