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