Add tap/drag touchpad gesture. (DO NOT MERGE)

The mapper sends a DOWN when the finger is released then starts
a short timer.  If the finger is not pressed again before the timer
elapses, then the mapper sends an UP to complete the tap.  If the
finger is pressed again then the mapper starts sending MOVEs
as part of a drag.

Double/triple taps work as intended because we also start watching
for a new tap when the finger is pressed again.  If a new tap
occurs the old tap is also finished.  So each individual finger
short press/release cycle constitutes a distinct tap.

Change-Id: Id0a6ee10dd27e723f6cb04e56b43939abe9e940c
This commit is contained in:
Jeff Brown
2011-04-19 21:20:10 -07:00
committed by Jeff Brown
parent 86ea1f5f52
commit 325bd07b31
3 changed files with 162 additions and 68 deletions

View File

@ -72,9 +72,14 @@ static const float DRAG_MIN_SWITCH_SPEED = 50.0f; // pixels per second
// 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 = 5.0f; // 5 pixels
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
@ -2752,14 +2757,21 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
}
}
// Process touches and virtual keys.
TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
if (touchResult == DISPATCH_TOUCH) {
suppressSwipeOntoVirtualKeys(when);
if (mPointerController != NULL) {
dispatchPointerGestures(when, policyFlags);
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);
}
dispatchTouches(when, policyFlags);
}
// Copy current touch to last touch in preparation for the next cycle.
@ -2772,6 +2784,12 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
}
}
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;
@ -3215,7 +3233,8 @@ void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags,
*outYPrecision = mLocked.orientedYPrecision;
}
void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags) {
void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags,
bool isTimeout) {
// Switch pointer presentation.
mPointerController->setPresentation(
mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
@ -3224,7 +3243,11 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag
// Update current gesture coordinates.
bool cancelPreviousGesture, finishPreviousGesture;
preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture);
bool sendEvents = preparePointerGestures(when,
&cancelPreviousGesture, &finishPreviousGesture, isTimeout);
if (!sendEvents) {
return;
}
// Show the pointer if needed.
if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL
@ -3237,7 +3260,9 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag
// 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
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;
@ -3325,27 +3350,6 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag
}
}
// 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,
@ -3372,13 +3376,49 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag
}
}
void TouchInputMapper::preparePointerGestures(nsecs_t when,
bool* outCancelPreviousGesture, bool* outFinishPreviousGesture) {
bool TouchInputMapper::preparePointerGestures(nsecs_t when,
bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) {
*outCancelPreviousGesture = false;
*outFinishPreviousGesture = false;
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();
mPointerController->setButtonState(0);
if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
mPointerGesture.spotIdBits.clear();
moveSpotsLocked();
}
return true;
}
}
// We did not handle this timeout.
return false;
}
// Update the velocity tracker.
{
VelocityTracker::Position positions[MAX_POINTERS];
@ -3433,7 +3473,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
// 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
} 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
@ -3468,7 +3508,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
moveSpotsLocked();
}
} else if (isPointerDown(mCurrentTouch.buttonState)) {
// Case 2: Button is pressed. (CLICK_OR_DRAG)
// 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.
//
@ -3482,11 +3522,11 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
// 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, "
LOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
"currentTouchPointerCount=%d", activeTouchId, mCurrentTouch.pointerCount);
#endif
// Reset state when just starting.
if (mPointerGesture.lastGestureMode != PointerGesture::CLICK_OR_DRAG) {
if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) {
*outFinishPreviousGesture = true;
mPointerGesture.activeGestureId = 0;
}
@ -3512,7 +3552,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerGesture.activeTouchId = activeTouchId = bestId;
activeTouchChanged = true;
#if DEBUG_GESTURES
LOGD("Gestures: CLICK_OR_DRAG switched pointers, "
LOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
"bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed);
#endif
}
@ -3538,7 +3578,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
float x, y;
mPointerController->getPosition(&x, &y);
mPointerGesture.currentGestureMode = PointerGesture::CLICK_OR_DRAG;
mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@ -3575,11 +3615,12 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
// Case 3. No fingers down and button is not pressed. (NEUTRAL)
*outFinishPreviousGesture = true;
// Watch for taps coming out of HOVER mode.
// Watch for taps coming out of HOVER or TAP_DRAG mode.
bool tapped = false;
if (mPointerGesture.lastGestureMode == PointerGesture::HOVER
if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER
|| mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG)
&& mLastTouch.pointerCount == 1) {
if (when <= mPointerGesture.tapTime + TAP_INTERVAL) {
if (when <= mPointerGesture.tapDownTime + TAP_INTERVAL) {
float x, y;
mPointerController->getPosition(&x, &y);
if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP
@ -3587,6 +3628,10 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
#if DEBUG_GESTURES
LOGD("Gestures: TAP");
#endif
mPointerGesture.tapUpTime = when;
getContext()->requestTimeoutAtTime(when + TAP_DRAG_INTERVAL);
mPointerGesture.activeGestureId = 0;
mPointerGesture.currentGestureMode = PointerGesture::TAP;
mPointerGesture.currentGestureIdBits.clear();
@ -3603,7 +3648,6 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
mPointerController->setButtonState(0);
if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_TAP;
@ -3624,8 +3668,8 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
}
} else {
#if DEBUG_GESTURES
LOGD("Gestures: Not a TAP, delay=%lld",
when - mPointerGesture.tapTime);
LOGD("Gestures: Not a TAP, %0.3fms since down",
(when - mPointerGesture.tapDownTime) * 0.000001f);
#endif
}
}
@ -3647,14 +3691,36 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
}
}
} else if (mCurrentTouch.pointerCount == 1) {
// Case 4. Exactly one finger down, button is not pressed. (HOVER)
// Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
// The pointer follows the active touch point.
// Emit HOVER_MOVE events at the pointer location.
assert(activeTouchId >= 0);
// 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: HOVER");
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;
}
if (mLastTouch.idBits.hasBit(activeTouchId)) {
const PointerData& currentPointer =
@ -3667,35 +3733,49 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
* mLocked.pointerGestureYMovementScale;
// Move the pointer using a relative motion.
// When using spots, the hover will occur at the position of the anchor spot.
// When using spots, the hover or drag will occur at the position of the anchor spot.
mPointerController->move(deltaX, deltaY);
}
*outFinishPreviousGesture = true;
mPointerGesture.activeGestureId = 0;
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.currentGestureMode = PointerGesture::HOVER;
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);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
down ? 1.0f : 0.0f);
mPointerController->setButtonState(down ? BUTTON_STATE_PRIMARY : 0);
if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
mPointerGesture.tapTime = when;
mPointerGesture.resetTap();
mPointerGesture.tapDownTime = when;
mPointerGesture.tapX = x;
mPointerGesture.tapY = y;
}
mPointerController->setButtonState(0);
if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_HOVER;
mPointerGesture.spotGesture = down ? PointerControllerInterface::SPOT_GESTURE_DRAG
: PointerControllerInterface::SPOT_GESTURE_HOVER;
mPointerGesture.spotIdBits.clear();
mPointerGesture.spotIdBits.markBit(activeTouchId);
mPointerGesture.spotIdToIndex[activeTouchId] = 0;
@ -4098,6 +4178,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
}
#endif
return true;
}
void TouchInputMapper::moveSpotsLocked() {

View File

@ -570,6 +570,7 @@ public:
const int32_t* keyCodes, uint8_t* outFlags);
virtual void fadePointer();
virtual void timeoutExpired(nsecs_t when);
protected:
Mutex mLock;
@ -935,10 +936,15 @@ private:
// Emits DOWN and UP events at the pointer location.
TAP,
// Exactly one finger dragging following a tap.
// Pointer follows the active finger.
// Emits DOWN, MOVE and UP events at the pointer location.
TAP_DRAG,
// 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,
BUTTON_CLICK_OR_DRAG,
// Exactly one finger, button is not pressed.
// Pointer follows the active finger.
@ -997,8 +1003,11 @@ private:
// Time the pointer gesture last went down.
nsecs_t downTime;
// Time we started waiting for a tap gesture.
nsecs_t tapTime;
// Time when the pointer went down for a TAP.
nsecs_t tapDownTime;
// Time when the pointer went up for a TAP.
nsecs_t tapUpTime;
// Location of initial tap.
float tapX, tapY;
@ -1030,12 +1039,13 @@ private:
spotIdBits.clear();
downTime = 0;
velocityTracker.clear();
resetTapTime();
resetTap();
resetQuietTime();
}
void resetTapTime() {
tapTime = LLONG_MIN;
void resetTap() {
tapDownTime = LLONG_MIN;
tapUpTime = LLONG_MIN;
}
void resetQuietTime() {
@ -1048,9 +1058,9 @@ private:
TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
void dispatchTouches(nsecs_t when, uint32_t policyFlags);
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);
void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
bool preparePointerGestures(nsecs_t when,
bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout);
void moveSpotsLocked();
// Dispatches a motion event.

View File

@ -91,6 +91,9 @@ public:
// Tap at current location.
// Briefly display one spot at the tapped location.
SPOT_GESTURE_TAP,
// Drag at current location.
// Display spot at pressed location.
SPOT_GESTURE_DRAG,
// Button pressed but no finger is down.
// Display spot at pressed location.
SPOT_GESTURE_BUTTON_CLICK,