diff options
-rw-r--r-- | include/ui/Input.h | 81 | ||||
-rw-r--r-- | libs/ui/Input.cpp | 89 | ||||
-rw-r--r-- | services/input/InputReader.cpp | 176 | ||||
-rw-r--r-- | services/input/InputReader.h | 20 |
4 files changed, 299 insertions, 67 deletions
diff --git a/include/ui/Input.h b/include/ui/Input.h index ba1c6b4..3b5aba4 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -674,6 +674,87 @@ private: int32_t mActivePointerId; }; + +/* + * Specifies parameters that govern pointer or wheel acceleration. + */ +struct VelocityControlParameters { + // A scale factor that is multiplied with the raw velocity deltas + // prior to applying any other velocity control factors. The scale + // factor should be used to adapt the input device resolution + // (eg. counts per inch) to the output device resolution (eg. pixels per inch). + // + // Must be a positive value. + // Default is 1.0 (no scaling). + float scale; + + // The scaled speed at which acceleration begins to be applied. + // This value establishes the upper bound of a low speed regime for + // small precise motions that are performed without any acceleration. + // + // Must be a non-negative value. + // Default is 0.0 (no low threshold). + float lowThreshold; + + // The scaled speed at which maximum acceleration is applied. + // The difference between highThreshold and lowThreshold controls + // the range of speeds over which the acceleration factor is interpolated. + // The wider the range, the smoother the acceleration. + // + // Must be a non-negative value greater than or equal to lowThreshold. + // Default is 0.0 (no high threshold). + float highThreshold; + + // The acceleration factor. + // When the speed is above the low speed threshold, the velocity will scaled + // by an interpolated value between 1.0 and this amount. + // + // Must be a positive greater than or equal to 1.0. + // Default is 1.0 (no acceleration). + float acceleration; + + VelocityControlParameters() : + scale(1.0f), lowThreshold(0.0f), highThreshold(0.0f), acceleration(1.0f) { + } + + VelocityControlParameters(float scale, float lowThreshold, + float highThreshold, float acceleration) : + scale(scale), lowThreshold(lowThreshold), + highThreshold(highThreshold), acceleration(acceleration) { + } +}; + +/* + * Implements mouse pointer and wheel speed control and acceleration. + */ +class VelocityControl { +public: + VelocityControl(); + + /* Sets the various parameters. */ + void setParameters(const VelocityControlParameters& parameters); + + /* Resets the current movement counters to zero. + * This has the effect of nullifying any acceleration. */ + void reset(); + + /* Translates a raw movement delta into an appropriately + * scaled / accelerated delta based on the current velocity. */ + void move(nsecs_t eventTime, float* deltaX, float* deltaY); + +private: + // If no movements are received within this amount of time, + // we assume the movement has stopped and reset the movement counters. + static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms + + VelocityControlParameters mParameters; + + nsecs_t mLastMovementTime; + VelocityTracker::Position mRawPosition; + VelocityTracker mVelocityTracker; +}; + + /* * Describes the characteristics and capabilities of an input device. */ diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp index 1ba38a7..e95dbe4 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -13,6 +13,10 @@ // Log debug messages about velocity tracking. #define DEBUG_VELOCITY 0 +// Log debug messages about acceleration. +#define DEBUG_ACCELERATION 0 + + #include <stdlib.h> #include <unistd.h> #include <ctype.h> @@ -20,6 +24,7 @@ #include <ui/Input.h> #include <math.h> +#include <limits.h> #ifdef HAVE_ANDROID_OS #include <binder/Parcel.h> @@ -693,6 +698,11 @@ bool MotionEvent::isTouchEvent(int32_t source, int32_t action) { // --- VelocityTracker --- +const uint32_t VelocityTracker::HISTORY_SIZE; +const nsecs_t VelocityTracker::MAX_AGE; +const nsecs_t VelocityTracker::MIN_WINDOW; +const nsecs_t VelocityTracker::MIN_DURATION; + VelocityTracker::VelocityTracker() { clear(); } @@ -902,6 +912,85 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const } +// --- VelocityControl --- + +const nsecs_t VelocityControl::STOP_TIME; + +VelocityControl::VelocityControl() { + reset(); +} + +void VelocityControl::setParameters(const VelocityControlParameters& parameters) { + mParameters = parameters; + reset(); +} + +void VelocityControl::reset() { + mLastMovementTime = LLONG_MIN; + mRawPosition.x = 0; + mRawPosition.y = 0; + mVelocityTracker.clear(); +} + +void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { + if ((deltaX && *deltaX) || (deltaY && *deltaY)) { + if (eventTime >= mLastMovementTime + STOP_TIME) { +#if DEBUG_ACCELERATION + LOGD("VelocityControl: stopped, last movement was %0.3fms ago", + (eventTime - mLastMovementTime) * 0.000001f); +#endif + reset(); + } + + mLastMovementTime = eventTime; + if (deltaX) { + mRawPosition.x += *deltaX; + } + if (deltaY) { + mRawPosition.y += *deltaY; + } + mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), &mRawPosition); + + float vx, vy; + float scale = mParameters.scale; + if (mVelocityTracker.getVelocity(0, &vx, &vy)) { + float speed = hypotf(vx, vy) * scale; + if (speed >= mParameters.highThreshold) { + // Apply full acceleration above the high speed threshold. + scale *= mParameters.acceleration; + } else if (speed > mParameters.lowThreshold) { + // Linearly interpolate the acceleration to apply between the low and high + // speed thresholds. + scale *= 1 + (speed - mParameters.lowThreshold) + / (mParameters.highThreshold - mParameters.lowThreshold) + * (mParameters.acceleration - 1); + } + +#if DEBUG_ACCELERATION + LOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " + "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration, + vx, vy, speed, scale / mParameters.scale); +#endif + } else { +#if DEBUG_ACCELERATION + LOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration); +#endif + } + + if (deltaX) { + *deltaX *= scale; + } + if (deltaY) { + *deltaY *= scale; + } + } +} + + // --- InputDeviceInfo --- InputDeviceInfo::InputDeviceInfo() { diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index cb64da6..8c9bad8 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -748,6 +748,20 @@ void InputReader::dump(String8& dump) { dump.appendFormat(INDENT2 "VirtualKeyQuietTime: %0.1fms\n", mConfig.virtualKeyQuietTime * 0.000001f); + dump.appendFormat(INDENT2 "PointerVelocityControlParameters: " + "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n", + mConfig.pointerVelocityControlParameters.scale, + mConfig.pointerVelocityControlParameters.lowThreshold, + mConfig.pointerVelocityControlParameters.highThreshold, + mConfig.pointerVelocityControlParameters.acceleration); + + dump.appendFormat(INDENT2 "WheelVelocityControlParameters: " + "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n", + mConfig.wheelVelocityControlParameters.scale, + mConfig.wheelVelocityControlParameters.lowThreshold, + mConfig.wheelVelocityControlParameters.highThreshold, + mConfig.wheelVelocityControlParameters.acceleration); + dump.appendFormat(INDENT2 "PointerGesture:\n"); dump.appendFormat(INDENT3 "QuietInterval: %0.1fms\n", mConfig.pointerGestureQuietInterval * 0.000001f); @@ -1430,6 +1444,10 @@ void CursorInputMapper::configure() { mHaveVWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_WHEEL); mHaveHWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_HWHEEL); + + mPointerVelocityControl.setParameters(getConfig()->pointerVelocityControlParameters); + mWheelXVelocityControl.setParameters(getConfig()->wheelVelocityControlParameters); + mWheelYVelocityControl.setParameters(getConfig()->wheelVelocityControlParameters); } void CursorInputMapper::configureParameters() { @@ -1491,6 +1509,11 @@ void CursorInputMapper::reset() { } } // release lock + // Reset velocity. + mPointerVelocityControl.reset(); + mWheelXVelocityControl.reset(); + mWheelYVelocityControl.reset(); + // Synthesize button up event on reset. nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); mAccumulator.clear(); @@ -1654,11 +1677,16 @@ void CursorInputMapper::sync(nsecs_t when) { } else { vscroll = 0; } + mWheelYVelocityControl.move(when, NULL, &vscroll); + if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) { hscroll = mAccumulator.relHWheel; } else { hscroll = 0; } + mWheelXVelocityControl.move(when, &hscroll, NULL); + + mPointerVelocityControl.move(when, &deltaX, &deltaY); if (mPointerController != NULL) { if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0 @@ -1900,6 +1928,7 @@ void TouchInputMapper::initializeLocked() { mLocked.orientedRanges.haveDistance = false; mPointerGesture.reset(); + mPointerGesture.pointerVelocityControl.setParameters(mConfig->pointerVelocityControlParameters); } void TouchInputMapper::configure() { @@ -2369,11 +2398,10 @@ bool TouchInputMapper::configureSurfaceLocked() { mLocked.associatedDisplayHeight); // Scale movements such that one whole swipe of the touch pad covers a - // given area relative to the diagonal size of the display. + // given area relative to the diagonal size of the display when no acceleration + // is applied. // 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 = mConfig->pointerGestureMovementSpeedRatio * displayDiagonal / rawDiagonal; mLocked.pointerGestureYMovementScale = mLocked.pointerGestureXMovementScale; @@ -3463,6 +3491,9 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag if (!sendEvents) { return; } + if (finishPreviousGesture) { + cancelPreviousGesture = false; + } // Switch pointer presentation. mPointerController->setPresentation( @@ -3666,6 +3697,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.pointerVelocityControl.reset(); + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; mPointerGesture.spotIdBits.clear(); @@ -3760,6 +3793,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, mPointerGesture.currentGestureMode = PointerGesture::QUIET; mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.pointerVelocityControl.reset(); + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; mPointerGesture.spotIdBits.clear(); @@ -3791,46 +3826,48 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, // Switch pointers if needed. // Find the fastest pointer and follow it. - if (activeTouchId >= 0) { - if (mCurrentTouch.pointerCount > 1) { - int32_t bestId = -1; - float bestSpeed = mConfig->pointerGestureDragMinSwitchSpeed; - for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { - uint32_t id = mCurrentTouch.pointers[i].id; - float vx, vy; - if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { - float speed = hypotf(vx, vy); - if (speed > bestSpeed) { - bestId = id; - bestSpeed = speed; - } + if (activeTouchId >= 0 && mCurrentTouch.pointerCount > 1) { + int32_t bestId = -1; + float bestSpeed = mConfig->pointerGestureDragMinSwitchSpeed; + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t id = mCurrentTouch.pointers[i].id; + float vx, vy; + if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { + float speed = hypotf(vx, vy); + if (speed > bestSpeed) { + bestId = id; + bestSpeed = speed; } } - if (bestId >= 0 && bestId != activeTouchId) { - mPointerGesture.activeTouchId = activeTouchId = bestId; - activeTouchChanged = true; + } + if (bestId >= 0 && bestId != activeTouchId) { + mPointerGesture.activeTouchId = activeTouchId = bestId; + activeTouchChanged = true; #if DEBUG_GESTURES - LOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, " - "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed); + LOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, " + "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed); #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; - - // Move the pointer using a relative motion. - // When using spots, the click will occur at the position of the anchor - // spot and all other spots will move there. - mPointerController->move(deltaX, deltaY); - } + if (activeTouchId >= 0 && 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; + + mPointerGesture.pointerVelocityControl.move(when, &deltaX, &deltaY); + + // Move the pointer using a relative motion. + // When using spots, the click will occur at the position of the anchor + // spot and all other spots will move there. + mPointerController->move(deltaX, deltaY); + } else { + mPointerGesture.pointerVelocityControl.reset(); } float x, y; @@ -3939,6 +3976,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, } } + mPointerGesture.pointerVelocityControl.reset(); + if (!tapped) { #if DEBUG_GESTURES LOGD("Gestures: NEUTRAL"); @@ -3995,9 +4034,13 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, float deltaY = (currentPointer.y - lastPointer.y) * mLocked.pointerGestureYMovementScale; + mPointerGesture.pointerVelocityControl.move(when, &deltaX, &deltaY); + // Move the pointer using a relative motion. // When using spots, the hover or drag will occur at the position of the anchor spot. mPointerController->move(deltaX, deltaY); + } else { + mPointerGesture.pointerVelocityControl.reset(); } bool down; @@ -4063,16 +4106,32 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, // a decision to transition into SWIPE or FREEFORM mode accordingly. LOG_ASSERT(activeTouchId >= 0); - bool needReference = false; bool settled = when >= mPointerGesture.firstTouchTime + mConfig->pointerGestureMultitouchSettleInterval; if (mPointerGesture.lastGestureMode != PointerGesture::PRESS && mPointerGesture.lastGestureMode != PointerGesture::SWIPE && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { *outFinishPreviousGesture = true; + } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) { + // Additional pointers have gone down but not yet settled. + // Reset the gesture. +#if DEBUG_GESTURES + LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, " + "settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when) + * 0.000001f); +#endif + *outCancelPreviousGesture = true; + } else { + // Continue previous gesture. + mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + } + + if (*outFinishPreviousGesture || *outCancelPreviousGesture) { mPointerGesture.currentGestureMode = PointerGesture::PRESS; mPointerGesture.activeGestureId = 0; mPointerGesture.referenceIdBits.clear(); + mPointerGesture.pointerVelocityControl.reset(); if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) { @@ -4093,37 +4152,18 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, mPointerGesture.referenceGestureX = c.getAxisValue(AMOTION_EVENT_AXIS_X); mPointerGesture.referenceGestureY = c.getAxisValue(AMOTION_EVENT_AXIS_Y); } else { + // Use the centroid and pointer location as the reference points for the gesture. #if DEBUG_GESTURES LOGD("Gestures: Using centroid as reference for MULTITOUCH, " "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when) * 0.000001f); #endif - needReference = true; + mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX, + &mPointerGesture.referenceTouchY); + mPointerController->getPosition(&mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); } - } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) { - // Additional pointers have gone down but not yet settled. - // Reset the gesture. -#if DEBUG_GESTURES - LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, " - "settle time remaining %0.3fms", - (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when) - * 0.000001f); -#endif - *outCancelPreviousGesture = true; - mPointerGesture.currentGestureMode = PointerGesture::PRESS; - mPointerGesture.activeGestureId = 0; - } else { - // Continue previous gesture. - mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; - } - - if (needReference) { - // Use the centroid and pointer location as the reference points for the gesture. - mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX, - &mPointerGesture.referenceTouchY); - mPointerController->getPosition(&mPointerGesture.referenceGestureX, - &mPointerGesture.referenceGestureY); } if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) { @@ -4253,10 +4293,14 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, mPointerGesture.referenceTouchX += commonDeltaX; mPointerGesture.referenceTouchY += commonDeltaY; - mPointerGesture.referenceGestureX += - commonDeltaX * mLocked.pointerGestureXMovementScale; - mPointerGesture.referenceGestureY += - commonDeltaY * mLocked.pointerGestureYMovementScale; + + commonDeltaX *= mLocked.pointerGestureXMovementScale; + commonDeltaY *= mLocked.pointerGestureYMovementScale; + mPointerGesture.pointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY); + + mPointerGesture.referenceGestureX += commonDeltaX; + mPointerGesture.referenceGestureY += commonDeltaY; + clampPositionUsingPointerBounds(mPointerController, &mPointerGesture.referenceGestureX, &mPointerGesture.referenceGestureY); diff --git a/services/input/InputReader.h b/services/input/InputReader.h index a7c6629..5abf404 100644 --- a/services/input/InputReader.h +++ b/services/input/InputReader.h @@ -62,6 +62,12 @@ struct InputReaderConfiguration { // Devices with these names will be ignored. Vector<String8> excludedDeviceNames; + // Velocity control parameters for mouse pointer movements. + VelocityControlParameters pointerVelocityControlParameters; + + // Velocity control parameters for mouse wheel movements. + VelocityControlParameters wheelVelocityControlParameters; + // Quiet time between certain pointer gesture transitions. // Time to allow for all fingers or buttons to settle into a stable state before // starting a new gesture. @@ -128,6 +134,8 @@ struct InputReaderConfiguration { filterTouchEvents(false), filterJumpyTouchEvents(false), virtualKeyQuietTime(0), + pointerVelocityControlParameters(1.0f, 80.0f, 400.0f, 4.0f), + wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f), pointerGestureQuietInterval(100 * 1000000LL), // 100 ms pointerGestureDragMinSwitchSpeed(50), // 50 pixels per second pointerGestureTapInterval(150 * 1000000LL), // 150 ms @@ -137,7 +145,7 @@ struct InputReaderConfiguration { pointerGestureMultitouchMinSpeed(150.0f), // 150 pixels per second pointerGestureSwipeTransitionAngleCosine(0.5f), // cosine of 45degrees pointerGestureSwipeMaxWidthRatio(0.333f), - pointerGestureMovementSpeedRatio(0.8f), + pointerGestureMovementSpeedRatio(0.5f), pointerGestureZoomSpeedRatio(0.3f) { } }; @@ -630,6 +638,12 @@ private: float mVWheelScale; float mHWheelScale; + // Velocity controls for mouse pointer and wheel movements. + // The controls for X and Y wheel movements are separate to keep them decoupled. + VelocityControl mPointerVelocityControl; + VelocityControl mWheelXVelocityControl; + VelocityControl mWheelYVelocityControl; + sp<PointerControllerInterface> mPointerController; struct LockedState { @@ -1160,6 +1174,9 @@ private: // A velocity tracker for determining whether to switch active pointers during drags. VelocityTracker velocityTracker; + // Velocity control for pointer movements. + VelocityControl pointerVelocityControl; + void reset() { firstTouchTime = LLONG_MIN; activeTouchId = -1; @@ -1174,6 +1191,7 @@ private: velocityTracker.clear(); resetTap(); resetQuietTime(); + pointerVelocityControl.reset(); } void resetTap() { |