diff options
Diffstat (limited to 'services/input/InputReader.cpp')
| -rw-r--r-- | services/input/InputReader.cpp | 2143 |
1 files changed, 1815 insertions, 328 deletions
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index 3029028..6003207 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -33,6 +33,8 @@ // 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 +56,67 @@ 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 = 150 * 1000000; // 150 ms + +// Tap drag gesture delay time. +// The time between up and the next up must be greater than this to be considered a +// drag. Otherwise, the previous tap is finished and a new tap begins. +static const nsecs_t TAP_DRAG_INTERVAL = 150 * 1000000; // 150 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 = 10.0f; // 10 pixels + +// Time after the first touch points go down to settle on an initial centroid. +// This is intended to be enough time to handle cases where the user puts down two +// fingers at almost but not quite exactly the same time. +static const nsecs_t MULTITOUCH_SETTLE_INTERVAL = 100 * 1000000; // 100ms + +// The transition from PRESS to SWIPE or FREEFORM gesture mode is made when +// both of the pointers are moving at least this fast. +static const float MULTITOUCH_MIN_SPEED = 150.0f; // pixels per second + +// The transition from PRESS to SWIPE gesture mode can only occur when the +// cosine of the angle between the two vectors is greater than or equal to than this value +// which indicates that the vectors are oriented in the same direction. +// When the vectors are oriented in the exactly same direction, the cosine is 1.0. +// (In exactly opposite directions, the cosine is -1.0.) +static const float SWIPE_TRANSITION_ANGLE_COSINE = 0.5f; // cosine of 45 degrees + +// The transition from PRESS to SWIPE gesture mode can only occur when the +// fingers are no more than this far apart relative to the diagonal size of +// the touch pad. For example, a ratio of 0.5 means that the fingers must be +// no more than half the diagonal size of the touch pad apart. +static const float SWIPE_MAX_WIDTH_RATIO = 0.333f; // 1/3 + +// The gesture movement speed factor relative to the size of the display. +// Movement speed applies when the fingers are moving in the same direction. +// Without acceleration, a full swipe of the touch pad diagonal in movement mode +// will cover this portion of the display diagonal. +static const float GESTURE_MOVEMENT_SPEED_RATIO = 0.8f; + +// The gesture zoom speed factor relative to the size of the display. +// Zoom speed applies when the fingers are mostly moving relative to each other +// to execute a scale gesture or similar. +// Without acceleration, a full swipe of the touch pad diagonal in zoom mode +// will cover this portion of the display diagonal. +static const float GESTURE_ZOOM_SPEED_RATIO = 0.3f; + + // --- Static Functions --- template<typename T> @@ -77,8 +140,8 @@ inline static float avg(float x, float y) { return (x + y) / 2; } -inline static float pythag(float x, float y) { - return sqrtf(x * x + y * y); +inline static float distance(float x1, float y1, float x2, float y2) { + return hypotf(x1 - x2, y1 - y2); } inline static int32_t signExtendNybble(int32_t value) { @@ -145,23 +208,32 @@ static uint32_t getButtonStateForScanCode(int32_t scanCode) { // Currently all buttons are mapped to the primary button. switch (scanCode) { case BTN_LEFT: + return AMOTION_EVENT_BUTTON_PRIMARY; case BTN_RIGHT: + return AMOTION_EVENT_BUTTON_SECONDARY; case BTN_MIDDLE: + return AMOTION_EVENT_BUTTON_TERTIARY; case BTN_SIDE: + return AMOTION_EVENT_BUTTON_BACK; case BTN_EXTRA: + return AMOTION_EVENT_BUTTON_FORWARD; case BTN_FORWARD: + return AMOTION_EVENT_BUTTON_FORWARD; case BTN_BACK: + return AMOTION_EVENT_BUTTON_BACK; case BTN_TASK: - return BUTTON_STATE_PRIMARY; default: return 0; } } // Returns true if the pointer should be reported as being down given the specified -// button states. -static bool isPointerDown(uint32_t buttonState) { - return buttonState & BUTTON_STATE_PRIMARY; +// button states. This determines whether the event is reported as a touch event. +static bool isPointerDown(int32_t buttonState) { + return buttonState & + (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY + | AMOTION_EVENT_BUTTON_TERTIARY + | AMOTION_EVENT_BUTTON_ERASER); } static int32_t calculateEdgeFlagsUsingPointerBounds( @@ -183,6 +255,60 @@ static int32_t calculateEdgeFlagsUsingPointerBounds( return edgeFlags; } +static void clampPositionUsingPointerBounds( + const sp<PointerControllerInterface>& pointerController, float* x, float* y) { + float minX, minY, maxX, maxY; + if (pointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + if (*x < minX) { + *x = minX; + } else if (*x > maxX) { + *x = maxX; + } + if (*y < minY) { + *y = minY; + } else if (*y > maxY) { + *y = maxY; + } + } +} + +static float calculateCommonVector(float a, float b) { + if (a > 0 && b > 0) { + return a < b ? a : b; + } else if (a < 0 && b < 0) { + return a > b ? a : b; + } else { + return 0; + } +} + +static void synthesizeButtonKey(InputReaderContext* context, int32_t action, + nsecs_t when, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState, + int32_t buttonState, int32_t keyCode) { + if ( + (action == AKEY_EVENT_ACTION_DOWN + && !(lastButtonState & buttonState) + && (currentButtonState & buttonState)) + || (action == AKEY_EVENT_ACTION_UP + && (lastButtonState & buttonState) + && !(currentButtonState & buttonState))) { + context->getDispatcher()->notifyKey(when, deviceId, source, policyFlags, + action, 0, keyCode, 0, context->getGlobalMetaState(), when); + } +} + +static void synthesizeButtonKeys(InputReaderContext* context, int32_t action, + nsecs_t when, int32_t deviceId, uint32_t source, + uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState) { + synthesizeButtonKey(context, action, when, deviceId, source, policyFlags, + lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK); + synthesizeButtonKey(context, action, when, deviceId, source, policyFlags, + lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD); +} + // --- InputReader --- @@ -190,7 +316,7 @@ InputReader::InputReader(const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& policy, const sp<InputDispatcherInterface>& dispatcher) : mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher), - mGlobalMetaState(0), mDisableVirtualKeysTimeout(-1) { + mGlobalMetaState(0), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX) { configureExcludedDevices(); updateGlobalMetaState(); updateInputConfiguration(); @@ -203,35 +329,61 @@ InputReader::~InputReader() { } void InputReader::loopOnce() { - RawEvent rawEvent; - mEventHub->getEvent(& rawEvent); + int32_t timeoutMillis = -1; + if (mNextTimeout != LLONG_MAX) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout); + } + size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); + if (count) { + processEvents(mEventBuffer, count); + } + if (!count || timeoutMillis == 0) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); #if DEBUG_RAW_EVENTS - LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d", - rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode, - rawEvent.value); + LOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); #endif - - process(& rawEvent); + mNextTimeout = LLONG_MAX; + timeoutExpired(now); + } } -void InputReader::process(const RawEvent* rawEvent) { - switch (rawEvent->type) { - case EventHubInterface::DEVICE_ADDED: - addDevice(rawEvent->deviceId); - break; - - case EventHubInterface::DEVICE_REMOVED: - removeDevice(rawEvent->deviceId); - break; - - case EventHubInterface::FINISHED_DEVICE_SCAN: - handleConfigurationChanged(rawEvent->when); - break; - - default: - consumeEvent(rawEvent); - break; +void InputReader::processEvents(const RawEvent* rawEvents, size_t count) { + for (const RawEvent* rawEvent = rawEvents; count;) { + int32_t type = rawEvent->type; + size_t batchSize = 1; + if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) { + int32_t deviceId = rawEvent->deviceId; + while (batchSize < count) { + if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT + || rawEvent[batchSize].deviceId != deviceId) { + break; + } + batchSize += 1; + } +#if DEBUG_RAW_EVENTS + LOGD("BatchSize: %d Count: %d", batchSize, count); +#endif + processEventsForDevice(deviceId, rawEvent, batchSize); + } else { + switch (rawEvent->type) { + case EventHubInterface::DEVICE_ADDED: + addDevice(rawEvent->deviceId); + break; + case EventHubInterface::DEVICE_REMOVED: + removeDevice(rawEvent->deviceId); + break; + case EventHubInterface::FINISHED_DEVICE_SCAN: + handleConfigurationChanged(rawEvent->when); + break; + default: + LOG_ASSERT(false); // can't happen + break; + } + } + count -= batchSize; + rawEvent += batchSize; } } @@ -352,9 +504,8 @@ InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, ui return device; } -void InputReader::consumeEvent(const RawEvent* rawEvent) { - int32_t deviceId = rawEvent->deviceId; - +void InputReader::processEventsForDevice(int32_t deviceId, + const RawEvent* rawEvents, size_t count) { { // acquire device registry reader lock RWLock::AutoRLock _rl(mDeviceRegistryLock); @@ -370,7 +521,20 @@ void InputReader::consumeEvent(const RawEvent* rawEvent) { return; } - device->process(rawEvent); + device->process(rawEvents, count); + } // release device registry reader lock +} + +void InputReader::timeoutExpired(nsecs_t when) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + if (!device->isIgnored()) { + device->timeoutExpired(when); + } + } } // release device registry reader lock } @@ -484,6 +648,12 @@ void InputReader::fadePointer() { } // release device registry reader lock } +void InputReader::requestTimeoutAtTime(nsecs_t when) { + if (when < mNextTimeout) { + mNextTimeout = when; + } +} + void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) { { // acquire state lock AutoMutex _l(mStateLock); @@ -713,11 +883,33 @@ void InputDevice::reset() { } } -void InputDevice::process(const RawEvent* rawEvent) { +void InputDevice::process(const RawEvent* rawEvents, size_t count) { + // Process all of the events in order for each mapper. + // We cannot simply ask each mapper to process them in bulk because mappers may + // have side-effects that must be interleaved. For example, joystick movement events and + // gamepad button presses are handled by different mappers but they should be dispatched + // in the order received. + size_t numMappers = mMappers.size(); + for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) { +#if DEBUG_RAW_EVENTS + LOGD("Input event: device=%d type=0x%04x scancode=0x%04x " + "keycode=0x%04x value=0x%04x flags=0x%08x", + rawEvent->deviceId, rawEvent->type, rawEvent->scanCode, rawEvent->keyCode, + rawEvent->value, rawEvent->flags); +#endif + + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->process(rawEvent); + } + } +} + +void InputDevice::timeoutExpired(nsecs_t when) { size_t numMappers = mMappers.size(); for (size_t i = 0; i < numMappers; i++) { InputMapper* mapper = mMappers[i]; - mapper->process(rawEvent); + mapper->timeoutExpired(when); } } @@ -812,6 +1004,9 @@ void InputMapper::configure() { void InputMapper::reset() { } +void InputMapper::timeoutExpired(nsecs_t when) { +} + int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { return AKEY_STATE_UNKNOWN; } @@ -1268,7 +1463,7 @@ void CursorInputMapper::dumpParameters(String8& dump) { dump.append(INDENT4 "Mode: navigation\n"); break; default: - assert(false); + LOG_ASSERT(false); } dump.appendFormat(INDENT4 "OrientationAware: %s\n", @@ -1284,7 +1479,7 @@ void CursorInputMapper::initializeLocked() { void CursorInputMapper::reset() { for (;;) { - uint32_t buttonState; + int32_t buttonState; { // acquire lock AutoMutex _l(mLock); @@ -1310,7 +1505,7 @@ void CursorInputMapper::reset() { void CursorInputMapper::process(const RawEvent* rawEvent) { switch (rawEvent->type) { case EV_KEY: { - uint32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode); + int32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode); if (buttonState) { if (rawEvent->value) { mAccumulator.buttonDown = buttonState; @@ -1368,12 +1563,16 @@ void CursorInputMapper::sync(nsecs_t when) { int32_t motionEventAction; int32_t motionEventEdgeFlags; + int32_t lastButtonState, currentButtonState; + PointerProperties pointerProperties; PointerCoords pointerCoords; nsecs_t downTime; float vscroll, hscroll; { // acquire lock AutoMutex _l(mLock); + lastButtonState = mLocked.buttonState; + bool down, downChanged; bool wasDown = isPointerDown(mLocked.buttonState); bool buttonsChanged = fields & Accumulator::FIELD_BUTTONS; @@ -1396,6 +1595,8 @@ void CursorInputMapper::sync(nsecs_t when) { downChanged = false; } + currentButtonState = mLocked.buttonState; + downTime = mLocked.downTime; float deltaX = fields & Accumulator::FIELD_REL_X ? mAccumulator.relX * mXScale : 0.0f; float deltaY = fields & Accumulator::FIELD_REL_Y ? mAccumulator.relY * mYScale : 0.0f; @@ -1439,14 +1640,40 @@ void CursorInputMapper::sync(nsecs_t when) { } } + motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + + pointerProperties.clear(); + pointerProperties.id = 0; + pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE; + pointerCoords.clear(); - motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) { + vscroll = mAccumulator.relWheel; + } else { + vscroll = 0; + } + if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) { + hscroll = mAccumulator.relHWheel; + } else { + hscroll = 0; + } if (mPointerController != NULL) { - mPointerController->move(deltaX, deltaY); - if (buttonsChanged) { - mPointerController->setButtonState(mLocked.buttonState); + if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0 + || buttonsChanged) { + mPointerController->setPresentation( + PointerControllerInterface::PRESENTATION_POINTER); + + if (deltaX != 0 || deltaY != 0) { + mPointerController->move(deltaX, deltaY); + } + + if (buttonsChanged) { + mPointerController->setButtonState(mLocked.buttonState); + } + + mPointerController->unfade(); } float x, y; @@ -1464,20 +1691,6 @@ void CursorInputMapper::sync(nsecs_t when) { } pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); - - if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) { - vscroll = mAccumulator.relWheel; - } else { - vscroll = 0; - } - if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) { - hscroll = mAccumulator.relHWheel; - } else { - hscroll = 0; - } - if (hscroll != 0 || vscroll != 0) { - mPointerController->unfade(); - } } // release lock // Moving an external trackball or mouse should wake the device. @@ -1489,22 +1702,41 @@ void CursorInputMapper::sync(nsecs_t when) { policyFlags |= POLICY_FLAG_WAKE_DROPPED; } + // Synthesize key down from buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource, + policyFlags, lastButtonState, currentButtonState); + + // Send motion event. int32_t metaState = mContext->getGlobalMetaState(); - int32_t pointerId = 0; getDispatcher()->notifyMotion(when, getDeviceId(), mSource, policyFlags, - motionEventAction, 0, metaState, motionEventEdgeFlags, - 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); + motionEventAction, 0, metaState, currentButtonState, motionEventEdgeFlags, + 1, &pointerProperties, &pointerCoords, mXPrecision, mYPrecision, downTime); - mAccumulator.clear(); + // Send hover move after UP to tell the application that the mouse is hovering now. + if (motionEventAction == AMOTION_EVENT_ACTION_UP + && mPointerController != NULL) { + getDispatcher()->notifyMotion(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, + metaState, currentButtonState, AMOTION_EVENT_EDGE_FLAG_NONE, + 1, &pointerProperties, &pointerCoords, mXPrecision, mYPrecision, downTime); + } + // Send scroll events. if (vscroll != 0 || hscroll != 0) { pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); getDispatcher()->notifyMotion(when, getDeviceId(), mSource, policyFlags, - AMOTION_EVENT_ACTION_SCROLL, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, - 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); + AMOTION_EVENT_ACTION_SCROLL, 0, metaState, currentButtonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + 1, &pointerProperties, &pointerCoords, mXPrecision, mYPrecision, downTime); } + + // Synthesize key up from buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource, + policyFlags, lastButtonState, currentButtonState); + + mAccumulator.clear(); } int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { @@ -1540,7 +1772,7 @@ TouchInputMapper::~TouchInputMapper() { } uint32_t TouchInputMapper::getSources() { - return mTouchSource; + return mTouchSource | mPointerSource; } void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { @@ -1579,6 +1811,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 +1852,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 "MaxSwipeWidth: %f\n", + mLocked.pointerGestureMaxSwipeWidth); + } } // release lock } @@ -1630,6 +1889,8 @@ void TouchInputMapper::initializeLocked() { mLocked.orientedRanges.haveTouchSize = false; mLocked.orientedRanges.haveToolSize = false; mLocked.orientedRanges.haveOrientation = false; + + mPointerGesture.reset(); } void TouchInputMapper::configure() { @@ -1642,12 +1903,18 @@ 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); + LOG_ASSERT(false); } // Configure absolute axis information. @@ -1671,14 +1938,30 @@ void TouchInputMapper::configureParameters() { mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents(); mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime(); + // TODO: Make this configurable. + //mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER; + mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS; + + 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 +1973,7 @@ void TouchInputMapper::configureParameters() { mParameters.associatedDisplayId = mParameters.orientationAware || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER ? 0 : -1; } @@ -1703,8 +1987,11 @@ 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); + LOG_ASSERT(false); } dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n", @@ -1776,6 +2063,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; @@ -1808,7 +2100,7 @@ bool TouchInputMapper::configureSurfaceLocked() { mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale); // Size of diagonal axis. - float diagonalSize = pythag(width, height); + float diagonalSize = hypotf(width, height); // TouchMajor and TouchMinor factors. if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) { @@ -1997,6 +2289,46 @@ 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; + float rawDiagonal = hypotf(rawWidth, rawHeight); + float displayDiagonal = hypotf(mLocked.associatedDisplayWidth, + mLocked.associatedDisplayHeight); + + // Scale movements such that one whole swipe of the touch pad covers a + // given area relative to the diagonal size of the display. + // 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 = GESTURE_MOVEMENT_SPEED_RATIO + * displayDiagonal / rawDiagonal; + 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 = GESTURE_ZOOM_SPEED_RATIO + * displayDiagonal / rawDiagonal; + mLocked.pointerGestureYZoomScale = mLocked.pointerGestureXZoomScale; + + // Max width between pointers to detect a swipe gesture is more than some fraction + // of the diagonal axis of the touch pad. Touches that are wider than this are + // translated into freeform gestures. + mLocked.pointerGestureMaxSwipeWidth = SWIPE_MAX_WIDTH_RATIO * rawDiagonal; + + // Reset the current pointer gesture. + mPointerGesture.reset(); + + // Remove any current spots. + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->clearSpots(); + } + } } return true; @@ -2303,7 +2635,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.touchSize.calibration: pressure\n"); break; default: - assert(false); + LOG_ASSERT(false); } // Tool Size @@ -2321,7 +2653,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.toolSize.calibration: area\n"); break; default: - assert(false); + LOG_ASSERT(false); } if (mCalibration.haveToolSizeLinearScale) { @@ -2361,7 +2693,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.pressure.calibration: amplitude\n"); break; default: - assert(false); + LOG_ASSERT(false); } switch (mCalibration.pressureSource) { @@ -2374,7 +2706,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { case Calibration::PRESSURE_SOURCE_DEFAULT: break; default: - assert(false); + LOG_ASSERT(false); } if (mCalibration.havePressureScale) { @@ -2391,7 +2723,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.size.calibration: normalized\n"); break; default: - assert(false); + LOG_ASSERT(false); } // Orientation @@ -2406,7 +2738,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.orientation.calibration: vector\n"); break; default: - assert(false); + LOG_ASSERT(false); } } @@ -2422,12 +2754,30 @@ void TouchInputMapper::reset() { { // acquire lock AutoMutex _l(mLock); initializeLocked(); + + if (mPointerController != NULL + && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->clearSpots(); + } } // release lock InputMapper::reset(); } void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { +#if DEBUG_RAW_EVENTS + if (!havePointerIds) { + LOGD("syncTouch: pointerCount=%d, no pointer ids", mCurrentTouch.pointerCount); + } else { + LOGD("syncTouch: pointerCount=%d, up=0x%08x, down=0x%08x, move=0x%08x, " + "last=0x%08x, current=0x%08x", mCurrentTouch.pointerCount, + mLastTouch.idBits.value & ~mCurrentTouch.idBits.value, + mCurrentTouch.idBits.value & ~mLastTouch.idBits.value, + mLastTouch.idBits.value & mCurrentTouch.idBits.value, + mLastTouch.idBits.value, mCurrentTouch.idBits.value); + } +#endif + // Preprocess pointer data. if (mParameters.useBadTouchFilter) { if (applyBadTouchFilter()) { @@ -2441,7 +2791,7 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { } } - if (! havePointerIds) { + if (!havePointerIds) { calculatePointerIds(); } @@ -2472,21 +2822,48 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { } } - // Process touches and virtual keys. - TouchResult touchResult = consumeOffScreenTouches(when, policyFlags); - if (touchResult == DISPATCH_TOUCH) { - suppressSwipeOntoVirtualKeys(when); - dispatchTouches(when, policyFlags); + // Synthesize key down from buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mTouchSource, + policyFlags, mLastTouch.buttonState, mCurrentTouch.buttonState); + + // Send motion events. + TouchResult touchResult; + if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount == 0 + && mLastTouch.buttonState == mCurrentTouch.buttonState) { + // Drop spurious syncs. + touchResult = DROP_STROKE; + } else { + // Process touches and virtual keys. + touchResult = consumeOffScreenTouches(when, policyFlags); + if (touchResult == DISPATCH_TOUCH) { + suppressSwipeOntoVirtualKeys(when); + if (mPointerController != NULL) { + dispatchPointerGestures(when, policyFlags, false /*isTimeout*/); + } + dispatchTouches(when, policyFlags); + } } + // Synthesize key up from buttons if needed. + synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mTouchSource, + policyFlags, mLastTouch.buttonState, mCurrentTouch.buttonState); + // 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); } } +void TouchInputMapper::timeoutExpired(nsecs_t when) { + if (mPointerController != NULL) { + dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/); + } +} + TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches( nsecs_t when, uint32_t policyFlags) { int32_t keyEventAction, keyEventFlags; @@ -2629,329 +3006,1394 @@ 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; + int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mCurrentTouch.buttonState; 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, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentTouchProperties, 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 = updateMovedPointers( + mCurrentTouchProperties, mCurrentTouchCoords, mCurrentTouch.idToIndex, + mLastTouchProperties, mLastTouchCoords, mLastTouch.idToIndex, + moveIdBits); + if (buttonState != mLastTouch.buttonState) { + moveNeeded = true; } - // 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, buttonState, 0, + mLastTouchProperties, 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); + LOG_ASSERT(moveIdBits.value == dispatchedIdBits.value); + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, 0, + mCurrentTouchProperties, 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, buttonState, edgeFlags, + mCurrentTouchProperties, mCurrentTouchCoords, + mCurrentTouch.idToIndex, dispatchedIdBits, downId, + xPrecision, yPrecision, mDownTime); } } + + // Update state for next time. + for (uint32_t i = 0; i < currentPointerCount; i++) { + mLastTouchProperties[i].copyFrom(mCurrentTouchProperties[i]); + mLastTouchCoords[i].copyFrom(mCurrentTouchCoords[i]); + } } -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; +void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags, + float* outXPrecision, float* outYPrecision) { + uint32_t currentPointerCount = mCurrentTouch.pointerCount; + uint32_t lastPointerCount = mLastTouch.pointerCount; - { // acquire lock - AutoMutex _l(mLock); + AutoMutex _l(mLock); + + // 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 + : 0; + if (mRawAxes.toolMinor.valid) { + toolMinor = in.toolMinor != 0 + ? in.toolMinor * mLocked.toolSizeLinearScale + + mLocked.toolSizeLinearBias + : 0; + } else { + toolMinor = toolMajor; + } + break; + case Calibration::TOOL_SIZE_CALIBRATION_AREA: + if (in.toolMajor != 0) { + float diameter = sqrtf(in.toolMajor + * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias); + toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias; + } else { + toolMajor = 0; + } + toolMinor = toolMajor; + break; + default: + toolMajor = 0; + toolMinor = 0; + break; + } + + if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) { + toolMajor /= currentPointerCount; + toolMinor /= currentPointerCount; + } + + // Pressure + float rawPressure; + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_PRESSURE: + rawPressure = in.pressure; + break; + case Calibration::PRESSURE_SOURCE_TOUCH: + rawPressure = in.touchMajor; + break; + default: + rawPressure = 0; + } + + float pressure; + switch (mCalibration.pressureCalibration) { + case Calibration::PRESSURE_CALIBRATION_PHYSICAL: + case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: + pressure = rawPressure * mLocked.pressureScale; + break; + default: + pressure = 1; + break; + } + + // TouchMajor and TouchMinor + float touchMajor, touchMinor; + switch (mCalibration.touchSizeCalibration) { + case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC: + touchMajor = in.touchMajor * mLocked.geometricScale; + if (mRawAxes.touchMinor.valid) { + touchMinor = in.touchMinor * mLocked.geometricScale; + } else { + touchMinor = touchMajor; + } + break; + case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: + touchMajor = toolMajor * pressure; + touchMinor = toolMinor * pressure; + break; + default: + touchMajor = 0; + touchMinor = 0; + break; + } + + if (touchMajor > toolMajor) { + touchMajor = toolMajor; + } + if (touchMinor > toolMinor) { + touchMinor = toolMinor; + } + + // Size + float size; + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_NORMALIZED: { + float rawSize = mRawAxes.toolMinor.valid + ? avg(in.toolMajor, in.toolMinor) + : in.toolMajor; + size = rawSize * mLocked.sizeScale; + break; + } + default: + size = 0; + break; + } + + // Orientation + float orientation; + switch (mCalibration.orientationCalibration) { + case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: + orientation = in.orientation * mLocked.orientationScale; + break; + 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 + hypotf(c1, c2) / 16.0f; + touchMajor *= scale; + touchMinor /= scale; + toolMajor *= scale; + toolMinor /= scale; + } else { + orientation = 0; + } + break; + } + default: + orientation = 0; + } + + // 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; + } + + // 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); + + // Write output properties. + PointerProperties& properties = mCurrentTouchProperties[i]; + properties.clear(); + properties.id = mCurrentTouch.pointers[i].id; + properties.toolType = getTouchToolType(mCurrentTouch.pointers[i].isStylus); + } + + // 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); + } + } - // 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++) { + *outXPrecision = mLocked.orientedXPrecision; + *outYPrecision = mLocked.orientedYPrecision; +} + +void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, + bool isTimeout) { + // Switch pointer presentation. + mPointerController->setPresentation( + mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS + ? PointerControllerInterface::PRESENTATION_SPOT + : PointerControllerInterface::PRESENTATION_POINTER); + + // Update current gesture coordinates. + bool cancelPreviousGesture, finishPreviousGesture; + bool sendEvents = preparePointerGestures(when, + &cancelPreviousGesture, &finishPreviousGesture, isTimeout); + if (!sendEvents) { + return; + } + + // Show the pointer if needed. + if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL + && mPointerGesture.currentGestureMode != PointerGesture::QUIET) { + mPointerController->unfade(); + } + + // Send events! + int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mCurrentTouch.buttonState; + + // Update last coordinates of pointers that have moved so that we observe the new + // pointer positions at the same time as other pointers that have just gone up. + bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP + || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG + || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG + || mPointerGesture.currentGestureMode == PointerGesture::PRESS + || mPointerGesture.currentGestureMode == PointerGesture::SWIPE + || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM; + bool moveNeeded = false; + if (down && !cancelPreviousGesture && !finishPreviousGesture + && !mPointerGesture.lastGestureIdBits.isEmpty() + && !mPointerGesture.currentGestureIdBits.isEmpty()) { + BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value + & mPointerGesture.lastGestureIdBits.value); + moveNeeded = updateMovedPointers(mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + movedGestureIdBits); + if (buttonState != mLastTouch.buttonState) { + moveNeeded = true; + } + } + + // Send motion events for all pointers that went up or were canceled. + BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits); + if (!dispatchedGestureIdBits.isEmpty()) { + if (cancelPreviousGesture) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clear(); + } else { + BitSet32 upGestureIdBits; + if (finishPreviousGesture) { + upGestureIdBits = dispatchedGestureIdBits; + } else { + upGestureIdBits.value = dispatchedGestureIdBits.value + & ~mPointerGesture.currentGestureIdBits.value; + } + while (!upGestureIdBits.isEmpty()) { + uint32_t id = upGestureIdBits.firstMarkedBit(); + upGestureIdBits.clearBit(id); + + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clearBit(id); + } + } + } + + // Send motion events for all pointers that moved. + if (moveNeeded) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Send motion events for all pointers that went down. + if (down) { + BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value + & ~dispatchedGestureIdBits.value); + while (!downGestureIdBits.isEmpty()) { + uint32_t id = downGestureIdBits.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; + } + + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, edgeFlags, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + } + } + + // Send motion events for hover. + if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Update state. + mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode; + if (!down) { + mPointerGesture.lastGestureIdBits.clear(); + } else { + mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits; + for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) { uint32_t id = idBits.firstMarkedBit(); idBits.clearBit(id); - uint32_t inIndex = touch->idToIndex[id]; + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + mPointerGesture.lastGestureProperties[index].copyFrom( + mPointerGesture.currentGestureProperties[index]); + mPointerGesture.lastGestureCoords[index].copyFrom( + mPointerGesture.currentGestureCoords[index]); + mPointerGesture.lastGestureIdToIndex[id] = index; + } + } +} - const PointerData& in = touch->pointers[inIndex]; +bool TouchInputMapper::preparePointerGestures(nsecs_t when, + bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) { + *outCancelPreviousGesture = false; + *outFinishPreviousGesture = false; - // 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; + AutoMutex _l(mLock); + + // Handle TAP timeout. + if (isTimeout) { +#if DEBUG_GESTURES + LOGD("Gestures: Processing timeout"); +#endif + + if (mPointerGesture.lastGestureMode == PointerGesture::TAP) { + if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) { + // The tap/drag timeout has not yet expired. + getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL); + } else { + // The tap is finished. +#if DEBUG_GESTURES + LOGD("Gestures: TAP finished"); +#endif + *outFinishPreviousGesture = true; + + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; + mPointerGesture.currentGestureIdBits.clear(); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; + mPointerGesture.spotIdBits.clear(); + moveSpotsLocked(); } - break; - case Calibration::TOOL_SIZE_CALIBRATION_LINEAR: - toolMajor = in.toolMajor != 0 - ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias - : 0; - if (mRawAxes.toolMinor.valid) { - toolMinor = in.toolMinor != 0 - ? in.toolMinor * mLocked.toolSizeLinearScale - + mLocked.toolSizeLinearBias - : 0; - } else { - toolMinor = toolMajor; + return true; + } + } + + // We did not handle this timeout. + return false; + } + + // 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 keep the same active touch id for as long as possible. + bool activeTouchChanged = false; + int32_t lastActiveTouchId = mPointerGesture.activeTouchId; + int32_t activeTouchId = lastActiveTouchId; + if (activeTouchId < 0) { + if (!mCurrentTouch.idBits.isEmpty()) { + activeTouchChanged = true; + activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit(); + mPointerGesture.firstTouchTime = when; + } + } else if (!mCurrentTouch.idBits.hasBit(activeTouchId)) { + activeTouchChanged = true; + if (!mCurrentTouch.idBits.isEmpty()) { + activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit(); + } else { + activeTouchId = mPointerGesture.activeTouchId = -1; + } + } + + // Determine whether we are in quiet time. + bool isQuietTime = false; + if (activeTouchId < 0) { + mPointerGesture.resetQuietTime(); + } else { + isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL; + if (!isQuietTime) { + if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS + || 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::BUTTON_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.currentGestureIdBits.clear(); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; + mPointerGesture.spotIdBits.clear(); + moveSpotsLocked(); + } + } else if (isPointerDown(mCurrentTouch.buttonState)) { + // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG) + // The pointer follows the active touch point. + // Emit DOWN, MOVE, UP events at the pointer location. + // + // Only the active touch matters; other fingers are ignored. This policy helps + // to handle the case where the user places a second finger on the touch pad + // to apply the necessary force to depress an integrated button below the surface. + // We don't want the second finger to be delivered to applications. + // + // For this to work well, we need to make sure to track the pointer that is really + // active. If the user first puts one finger down to click then adds another + // finger to drag then the active pointer should switch to the finger that is + // being dragged. +#if DEBUG_GESTURES + LOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, " + "currentTouchPointerCount=%d", activeTouchId, mCurrentTouch.pointerCount); +#endif + // Reset state when just starting. + if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) { + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = 0; + } + + // Switch pointers if needed. + // Find the fastest pointer and follow it. + if (activeTouchId >= 0) { + 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 = hypotf(vx, vy); + if (speed > bestSpeed) { + bestId = id; + bestSpeed = speed; + } + } } - 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; + if (bestId >= 0 && bestId != activeTouchId) { + mPointerGesture.activeTouchId = activeTouchId = bestId; + activeTouchChanged = true; +#if DEBUG_GESTURES + LOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, " + "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed); +#endif } - toolMinor = toolMajor; - break; - default: - toolMajor = 0; - toolMinor = 0; - break; } - if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) { - toolMajor /= pointerCount; - toolMinor /= pointerCount; + 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; + + // Move the pointer using a relative motion. + // When using spots, the click will occur at the position of the anchor + // spot and all other spots will move there. + mPointerController->move(deltaX, deltaY); } + } - // 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 x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + if (activeTouchId >= 0) { + // Collapse all spots into one point at the pointer location. + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_DRAG; + mPointerGesture.spotIdBits.clear(); + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t id = mCurrentTouch.pointers[i].id; + mPointerGesture.spotIdBits.markBit(id); + mPointerGesture.spotIdToIndex[id] = i; + mPointerGesture.spotCoords[i] = mPointerGesture.currentGestureCoords[0]; + } + } else { + // No fingers. Generate a spot at the pointer location so the + // anchor appears to be pressed. + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_CLICK; + mPointerGesture.spotIdBits.clear(); + mPointerGesture.spotIdBits.markBit(0); + mPointerGesture.spotIdToIndex[0] = 0; + mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0]; } + moveSpotsLocked(); + } + } 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 TAP_DRAG mode. + bool tapped = false; + if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER + || mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) + && mLastTouch.pointerCount == 1) { + if (when <= mPointerGesture.tapDownTime + TAP_INTERVAL) { + float x, y; + mPointerController->getPosition(&x, &y); + if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP + && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) { +#if DEBUG_GESTURES + LOGD("Gestures: TAP"); +#endif - float pressure; - switch (mCalibration.pressureCalibration) { - case Calibration::PRESSURE_CALIBRATION_PHYSICAL: - case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: - pressure = rawPressure * mLocked.pressureScale; - break; - default: - pressure = 1; - break; - } + mPointerGesture.tapUpTime = when; + getContext()->requestTimeoutAtTime(when + TAP_DRAG_INTERVAL); + + mPointerGesture.activeGestureId = 0; + mPointerGesture.currentGestureMode = PointerGesture::TAP; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit( + mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[ + mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = + mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_X, mPointerGesture.tapX); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_TAP; + mPointerGesture.spotIdBits.clear(); + mPointerGesture.spotIdBits.markBit(lastActiveTouchId); + mPointerGesture.spotIdToIndex[lastActiveTouchId] = 0; + mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0]; + moveSpotsLocked(); + } - // 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; + tapped = true; } else { - touchMinor = touchMajor; +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, + y - mPointerGesture.tapY); +#endif } - break; - case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: - touchMajor = toolMajor * pressure; - touchMinor = toolMinor * pressure; - break; - default: - touchMajor = 0; - touchMinor = 0; - break; + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP, %0.3fms since down", + (when - mPointerGesture.tapDownTime) * 0.000001f); +#endif } + } - if (touchMajor > toolMajor) { - touchMajor = toolMajor; + if (!tapped) { +#if DEBUG_GESTURES + LOGD("Gestures: NEUTRAL"); +#endif + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; + mPointerGesture.currentGestureIdBits.clear(); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; + mPointerGesture.spotIdBits.clear(); + moveSpotsLocked(); } - if (touchMinor > toolMinor) { - touchMinor = toolMinor; + } + } else if (mCurrentTouch.pointerCount == 1) { + // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG) + // The pointer follows the active touch point. + // When in HOVER, emit HOVER_MOVE events at the pointer location. + // When in TAP_DRAG, emit MOVE events at the pointer location. + LOG_ASSERT(activeTouchId >= 0); + + mPointerGesture.currentGestureMode = PointerGesture::HOVER; + if (mPointerGesture.lastGestureMode == PointerGesture::TAP) { + if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) { + float x, y; + mPointerController->getPosition(&x, &y); + if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP + && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) { + mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG; + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, + y - mPointerGesture.tapY); +#endif + } + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up", + (when - mPointerGesture.tapUpTime) * 0.000001f); +#endif } + } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) { + mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG; + } - // 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; + 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; + + // Move the pointer using a relative motion. + // When using spots, the hover or drag will occur at the position of the anchor spot. + mPointerController->move(deltaX, deltaY); + } + + bool down; + if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) { +#if DEBUG_GESTURES + LOGD("Gestures: TAP_DRAG"); +#endif + down = true; + } else { +#if DEBUG_GESTURES + LOGD("Gestures: HOVER"); +#endif + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = 0; + down = false; + } + + float x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, + down ? 1.0f : 0.0f); + + if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) { + mPointerGesture.resetTap(); + mPointerGesture.tapDownTime = when; + mPointerGesture.tapX = x; + mPointerGesture.tapY = y; + } + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = down ? PointerControllerInterface::SPOT_GESTURE_DRAG + : PointerControllerInterface::SPOT_GESTURE_HOVER; + mPointerGesture.spotIdBits.clear(); + mPointerGesture.spotIdBits.markBit(activeTouchId); + mPointerGesture.spotIdToIndex[activeTouchId] = 0; + mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0]; + moveSpotsLocked(); + } + } else { + // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM) + // We need to provide feedback for each finger that goes down so we cannot wait + // for the fingers to move before deciding what to do. + // + // The ambiguous case is deciding what to do when there are two fingers down but they + // have not moved enough to determine whether they are part of a drag or part of a + // freeform gesture, or just a press or long-press at the pointer location. + // + // When there are two fingers we start with the PRESS hypothesis and we generate a + // down at the pointer location. + // + // When the two fingers move enough or when additional fingers are added, we make + // a decision to transition into SWIPE or FREEFORM mode accordingly. + LOG_ASSERT(activeTouchId >= 0); + + bool needReference = false; + bool settled = when >= mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL; + if (mPointerGesture.lastGestureMode != PointerGesture::PRESS + && mPointerGesture.lastGestureMode != PointerGesture::SWIPE + && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + *outFinishPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::PRESS; + mPointerGesture.activeGestureId = 0; + + if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS + && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) { + // The spot is already visible and has settled, use it as the reference point + // for the gesture. Other spots will be positioned relative to this one. +#if DEBUG_GESTURES + LOGD("Gestures: Using active spot as reference for MULTITOUCH, " + "settle time expired %0.3fms ago", + (when - mPointerGesture.firstTouchTime - MULTITOUCH_SETTLE_INTERVAL) + * 0.000001f); +#endif + const PointerData& d = mLastTouch.pointers[mLastTouch.idToIndex[ + mPointerGesture.activeTouchId]]; + mPointerGesture.referenceTouchX = d.x; + mPointerGesture.referenceTouchY = d.y; + const PointerCoords& c = mPointerGesture.spotCoords[mPointerGesture.spotIdToIndex[ + mPointerGesture.activeTouchId]]; + mPointerGesture.referenceGestureX = c.getAxisValue(AMOTION_EVENT_AXIS_X); + mPointerGesture.referenceGestureY = c.getAxisValue(AMOTION_EVENT_AXIS_Y); + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Using centroid as reference for MULTITOUCH, " + "settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when) + * 0.000001f); +#endif + needReference = true; } - default: - size = 0; - break; + } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) { + // Additional pointers have gone down but not yet settled. + // Reset the gesture. +#if DEBUG_GESTURES + LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, " + "settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when) + * 0.000001f); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::PRESS; + mPointerGesture.activeGestureId = 0; + } else { + // Continue previous gesture. + mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + } + + if (needReference) { + // Use the centroid and pointer location as the reference points for the gesture. + mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX, + &mPointerGesture.referenceTouchY); + mPointerController->getPosition(&mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); + } + + if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) { + float d; + if (mCurrentTouch.pointerCount > 2) { + // There are more than two pointers, switch to FREEFORM. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", + mCurrentTouch.pointerCount); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } else if (((d = distance( + mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y, + mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y)) + > mLocked.pointerGestureMaxSwipeWidth)) { + // There are two pointers but they are too far apart, switch to FREEFORM. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f", + d, mLocked.pointerGestureMaxSwipeWidth); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } else { + // There are two pointers. Wait for both pointers to start moving + // before deciding whether this is a SWIPE or FREEFORM gesture. + uint32_t id1 = mCurrentTouch.pointers[0].id; + uint32_t id2 = mCurrentTouch.pointers[1].id; + + float vx1, vy1, vx2, vy2; + mPointerGesture.velocityTracker.getVelocity(id1, &vx1, &vy1); + mPointerGesture.velocityTracker.getVelocity(id2, &vx2, &vy2); + + float speed1 = hypotf(vx1, vy1); + float speed2 = hypotf(vx2, vy2); + if (speed1 >= MULTITOUCH_MIN_SPEED && speed2 >= MULTITOUCH_MIN_SPEED) { + // Calculate the dot product of the velocity 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). + float dot = vx1 * vx2 + vy1 * vy2; + float cosine = dot / (speed1 * speed2); // denominator always > 0 + if (cosine >= SWIPE_TRANSITION_ANGLE_COSINE) { + // Pointers are moving in the same direction. Switch to SWIPE. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to SWIPE, " + "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, " + "cosine %0.3f >= %0.3f", + speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED, + cosine, SWIPE_TRANSITION_ANGLE_COSINE); +#endif + mPointerGesture.currentGestureMode = PointerGesture::SWIPE; + } else { + // Pointers are moving in different directions. Switch to FREEFORM. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to FREEFORM, " + "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, " + "cosine %0.3f < %0.3f", + speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED, + cosine, SWIPE_TRANSITION_ANGLE_COSINE); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } + } } + } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // Switch from SWIPE to FREEFORM if additional pointers go down. + // Cancel previous gesture. + if (mCurrentTouch.pointerCount > 2) { +#if DEBUG_GESTURES + LOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", + mCurrentTouch.pointerCount); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } + } - // 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; + // Move the reference points based on the overall group motion of the fingers. + // The objective is to calculate a vector delta that is common to the movement + // of all fingers. + BitSet32 commonIdBits(mLastTouch.idBits.value & mCurrentTouch.idBits.value); + if (!commonIdBits.isEmpty()) { + float commonDeltaX = 0, commonDeltaY = 0; + for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) { + bool first = (idBits == commonIdBits); + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + + const PointerData& cpd = mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]]; + const PointerData& lpd = mLastTouch.pointers[mLastTouch.idToIndex[id]]; + float deltaX = cpd.x - lpd.x; + float deltaY = cpd.y - lpd.y; + + if (first) { + commonDeltaX = deltaX; + commonDeltaY = deltaY; } else { - orientation = 0; + commonDeltaX = calculateCommonVector(commonDeltaX, deltaX); + commonDeltaY = calculateCommonVector(commonDeltaY, deltaY); } - break; - } - default: - orientation = 0; } - // 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; + mPointerGesture.referenceTouchX += commonDeltaX; + mPointerGesture.referenceTouchY += commonDeltaY; + mPointerGesture.referenceGestureX += + commonDeltaX * mLocked.pointerGestureXMovementScale; + mPointerGesture.referenceGestureY += + commonDeltaY * mLocked.pointerGestureYMovementScale; + clampPositionUsingPointerBounds(mPointerController, + &mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); + } + + // Report gestures. + if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) { + // PRESS mode. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount); +#endif + LOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_PRESS; + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // SWIPE mode. +#if DEBUG_GESTURES + LOGD("Gestures: SWIPE activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount); +#endif + LOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = + AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_SWIPE; + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) { + // FREEFORM mode. +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount); +#endif + LOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + + BitSet32 mappedTouchIdBits; + BitSet32 usedGestureIdBits; + if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + // Initially, assign the active gesture id to the active touch point + // if there is one. No other touch id bits are mapped yet. + if (!*outCancelPreviousGesture) { + mappedTouchIdBits.markBit(activeTouchId); + usedGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = + mPointerGesture.activeGestureId; + } else { + mPointerGesture.activeGestureId = -1; } - 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; + } 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; + } } - 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 DEBUG_GESTURES + LOGD("Gestures: FREEFORM follow up " + "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " + "activeGestureId=%d", + mappedTouchIdBits.value, usedGestureIdBits.value, + mPointerGesture.activeGestureId); +#endif + + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; 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.referenceTouchX) + * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX; + float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY) + * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY; + + mPointerGesture.currentGestureProperties[i].clear(); + mPointerGesture.currentGestureProperties[i].id = gestureId; + mPointerGesture.currentGestureProperties[i].toolType = + AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER; + 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); } - } - // 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.activeGestureId < 0) { + mPointerGesture.activeGestureId = + mPointerGesture.currentGestureIdBits.firstMarkedBit(); +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM new " + "activeGestureId=%d", 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); + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_FREEFORM; } - 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); + } + + // Update spot locations for PRESS, SWIPE and FREEFORM. + // We use the same calculation as we do to calculate the gesture pointers + // for FREEFORM so that the spots smoothly track gestures. + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotIdBits.clear(); + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t id = mCurrentTouch.pointers[i].id; + mPointerGesture.spotIdBits.markBit(id); + mPointerGesture.spotIdToIndex[id] = i; + + float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX) + * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX; + float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY) + * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY; + + mPointerGesture.spotCoords[i].clear(); + mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); } + moveSpotsLocked(); + } + } + + mPointerController->setButtonState(mCurrentTouch.buttonState); + +#if DEBUG_GESTURES + LOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " + "currentGestureMode=%d, currentGestureIdBits=0x%08x, " + "lastGestureMode=%d, lastGestureIdBits=0x%08x", + toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), + mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value, + mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value); + for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.currentGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; + LOGD(" currentGesture[%d]: index=%d, toolType=%d, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, properties.toolType, + coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } + for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.lastGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; + LOGD(" lastGesture[%d]: index=%d, toolType=%d, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, properties.toolType, + coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } +#endif + return true; +} + +void TouchInputMapper::moveSpotsLocked() { + mPointerController->setSpots(mPointerGesture.spotGesture, + mPointerGesture.spotCoords, mPointerGesture.spotIdToIndex, mPointerGesture.spotIdBits); +} + +void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, + int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, + const PointerProperties* properties, const PointerCoords* coords, + const uint32_t* idToIndex, BitSet32 idBits, + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) { + PointerCoords pointerCoords[MAX_POINTERS]; + PointerProperties pointerProperties[MAX_POINTERS]; + uint32_t pointerCount = 0; + while (!idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = idToIndex[id]; + pointerProperties[pointerCount].copyFrom(properties[index]); + pointerCoords[pointerCount].copyFrom(coords[index]); + + if (changedId >= 0 && id == uint32_t(changedId)) { + action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; } - xPrecision = mLocked.orientedXPrecision; - yPrecision = mLocked.orientedYPrecision; + pointerCount += 1; + } + + LOG_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. + LOG_ASSERT(false); + } + } + + getDispatcher()->notifyMotion(when, getDeviceId(), source, policyFlags, + action, flags, metaState, buttonState, edgeFlags, + pointerCount, pointerProperties, pointerCoords, xPrecision, yPrecision, downTime); +} + +bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties, + const PointerCoords* inCoords, const uint32_t* inIdToIndex, + PointerProperties* outProperties, PointerCoords* outCoords, const uint32_t* outIdToIndex, + BitSet32 idBits) const { + bool changed = false; + while (!idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + + uint32_t inIndex = inIdToIndex[id]; + uint32_t outIndex = outIdToIndex[id]; + + const PointerProperties& curInProperties = inProperties[inIndex]; + const PointerCoords& curInCoords = inCoords[inIndex]; + PointerProperties& curOutProperties = outProperties[outIndex]; + PointerCoords& curOutCoords = outCoords[outIndex]; + + if (curInProperties != curOutProperties) { + curOutProperties.copyFrom(curInProperties); + changed = true; + } + + if (curInCoords != curOutCoords) { + curOutCoords.copyFrom(curInCoords); + changed = true; + } + } + return changed; +} + +void TouchInputMapper::fadePointer() { + { // acquire lock + AutoMutex _l(mLock); + if (mPointerController != NULL) { + mPointerController->fade(); + } } // release lock +} - getDispatcher()->notifyMotion(when, getDeviceId(), mTouchSource, policyFlags, - motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags, - pointerCount, pointerIds, pointerCoords, - xPrecision, yPrecision, mDownTime); +int32_t TouchInputMapper::getTouchToolType(bool isStylus) const { + if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) { + return isStylus ? AMOTION_EVENT_TOOL_TYPE_STYLUS : AMOTION_EVENT_TOOL_TYPE_FINGER; + } else { + return isStylus ? AMOTION_EVENT_TOOL_TYPE_INDIRECT_STYLUS + : AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER; + } } bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) { @@ -3080,7 +4522,7 @@ void TouchInputMapper::calculatePointerIds() { // Previous iterations consumed the root element of the heap. // Pop root element off of the heap (sift down). heapSize -= 1; - assert(heapSize > 0); + LOG_ASSERT(heapSize > 0); // Sift down. heap[0] = heap[heapSize]; @@ -3592,6 +5034,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 +5054,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) { + int32_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 +5127,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) { @@ -3684,8 +5144,10 @@ void SingleTouchInputMapper::sync(nsecs_t when) { mCurrentTouch.pointers[0].toolMajor = mToolWidth; mCurrentTouch.pointers[0].toolMinor = mToolWidth; mCurrentTouch.pointers[0].orientation = 0; + mCurrentTouch.pointers[0].isStylus = false; // TODO: Set stylus mCurrentTouch.idToIndex[0] = 0; mCurrentTouch.idBits.markBit(0); + mCurrentTouch.buttonState = mButtonState; } syncTouch(when, true); @@ -3715,6 +5177,7 @@ MultiTouchInputMapper::~MultiTouchInputMapper() { void MultiTouchInputMapper::initialize() { mAccumulator.clear(); + mButtonState = 0; } void MultiTouchInputMapper::reset() { @@ -3725,6 +5188,20 @@ void MultiTouchInputMapper::reset() { void MultiTouchInputMapper::process(const RawEvent* rawEvent) { switch (rawEvent->type) { + case EV_KEY: { + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + int32_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]; @@ -3874,6 +5351,8 @@ void MultiTouchInputMapper::sync(nsecs_t when) { outPointer.orientation = 0; } + outPointer.isStylus = false; // TODO: Handle stylus + // Assign pointer id using tracking id if available. if (havePointerIds) { if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) { @@ -3902,6 +5381,9 @@ void MultiTouchInputMapper::sync(nsecs_t when) { mCurrentTouch.pointerCount = outCount; + mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp; + mCurrentTouch.buttonState = mButtonState; + syncTouch(when, havePointerIds); mAccumulator.clear(); @@ -4181,6 +5663,12 @@ void JoystickInputMapper::sync(nsecs_t when, bool force) { } int32_t metaState = mContext->getGlobalMetaState(); + int32_t buttonState = 0; + + PointerProperties pointerProperties; + pointerProperties.clear(); + pointerProperties.id = 0; + pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN; PointerCoords pointerCoords; pointerCoords.clear(); @@ -4200,10 +5688,9 @@ void JoystickInputMapper::sync(nsecs_t when, bool force) { // TODO: Use the input device configuration to control this behavior more finely. uint32_t policyFlags = 0; - int32_t pointerId = 0; getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, policyFlags, - AMOTION_EVENT_ACTION_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, - 1, &pointerId, &pointerCoords, 0, 0, 0); + AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + 1, &pointerProperties, &pointerCoords, 0, 0, 0); } bool JoystickInputMapper::filterAxes(bool force) { |
