Loading libs/input/VelocityTracker.cpp +56 −41 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ #include <utils/BitSet.h> #include <utils/Timers.h> using std::literals::chrono_literals::operator""ms; namespace android { /** Loading Loading @@ -57,8 +59,14 @@ static const nsecs_t NANOS_PER_MS = 1000000; // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the // velocity after the pointer starts moving again. static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms; static std::string toString(std::chrono::nanoseconds t) { std::stringstream stream; stream.precision(1); stream << std::fixed << std::chrono::duration<float, std::milli>(t).count() << " ms"; return stream.str(); } static float vectorDot(const float* a, const float* b, uint32_t m) { float r = 0; Loading Loading @@ -146,18 +154,14 @@ std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy( VelocityTracker::Strategy strategy) { switch (strategy) { case VelocityTracker::Strategy::IMPULSE: if (DEBUG_STRATEGY) { ALOGI("Initializing impulse strategy"); } ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy"); return std::make_unique<ImpulseVelocityTrackerStrategy>(); case VelocityTracker::Strategy::LSQ1: return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1); case VelocityTracker::Strategy::LSQ2: if (DEBUG_STRATEGY && !DEBUG_IMPULSE) { ALOGI("Initializing lsq2 strategy"); } ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy"); return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2); case VelocityTracker::Strategy::LSQ3: Loading Loading @@ -221,12 +225,11 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, idBits.clearLastMarkedBit(); } if ((mCurrentPointerIdBits.value & idBits.value) && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { if (DEBUG_VELOCITY) { ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", (eventTime - mLastEventTime) * 0.000001f); } if ((mCurrentPointerIdBits.value & idBits.value) && std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) { ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.", toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str()); // We have not received any movements for too long. Assume that all pointers // have stopped. mStrategy->clear(); Loading Loading @@ -281,8 +284,18 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: break; default: // Ignore all other actions because they do not convey any new information about case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_UP: { std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime); if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) { ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.", toString(delaySinceLastEvent).c_str()); // We have not received any movements for too long. Assume that all pointers // have stopped. mStrategy->clear(); } // These actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position // of the pointers that went up. ACTION_POINTER_UP does include the new position of Loading @@ -292,6 +305,10 @@ void VelocityTracker::addMovement(const MotionEvent* event) { // before adding the movement. return; } default: // Ignore all other actions. return; } size_t pointerCount = event->getPointerCount(); if (pointerCount > MAX_POINTERS) { Loading Loading @@ -438,10 +455,10 @@ void LeastSquaresVelocityTrackerStrategy::addMovement( static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y, const std::vector<float>& w, uint32_t n, float* outB, float* outDet) { const size_t m = x.size(); if (DEBUG_STRATEGY) { ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); } LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes"); // Expand the X vector to a matrix A, pre-multiplied by the weights. Loading @@ -452,9 +469,9 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo a[i][h] = a[i - 1][h] * x[h]; } } if (DEBUG_STRATEGY) { ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str()); } ALOGD_IF(DEBUG_STRATEGY, " - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str()); // Apply the Gram-Schmidt process to A to obtain its QR decomposition. float q[n][m]; // orthonormal basis, column-major order Loading @@ -473,9 +490,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo float norm = vectorNorm(&q[j][0], m); if (norm < 0.000001f) { // vectors are linearly dependent or zero so no solution if (DEBUG_STRATEGY) { ALOGD(" - no solution, norm=%f", norm); } ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm); return false; } Loading Loading @@ -518,9 +533,8 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo } outB[i] /= r[i][i]; } if (DEBUG_STRATEGY) { ALOGD(" - b=%s", vectorToString(outB, n).c_str()); } ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB, n).c_str()); // Calculate the coefficient of determination as 1 - (SSerr / SStot) where // SSerr is the residual sum of squares (variance of the error), Loading @@ -546,11 +560,11 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo sstot += w[h] * w[h] * var * var; } *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; if (DEBUG_STRATEGY) { ALOGD(" - sserr=%f", sserr); ALOGD(" - sstot=%f", sstot); ALOGD(" - det=%f", *outDet); } ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); return true; } Loading Loading @@ -673,11 +687,11 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; outEstimator->confidence = xdet * ydet; if (DEBUG_STRATEGY) { ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); } return true; } } Loading Loading @@ -1185,9 +1199,10 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; if (DEBUG_STRATEGY) { ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); } ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons Loading libs/input/tests/VelocityTracker_test.cpp +66 −7 Original line number Diff line number Diff line Loading @@ -26,7 +26,9 @@ #include <gui/constants.h> #include <input/VelocityTracker.h> using namespace std::chrono_literals; using std::literals::chrono_literals::operator""ms; using std::literals::chrono_literals::operator""ns; using std::literals::chrono_literals::operator""us; using android::base::StringPrintf; namespace android { Loading Loading @@ -149,8 +151,7 @@ static std::vector<MotionEvent> createMotionEventStream( if (i == 0) { action = AMOTION_EVENT_ACTION_DOWN; EXPECT_EQ(1U, pointerCount) << "First event should only have 1 pointer"; } else if (i == motions.size() - 1) { EXPECT_EQ(1U, pointerCount) << "Last event should only have 1 pointer"; } else if ((i == motions.size() - 1) && pointerCount == 1) { action = AMOTION_EVENT_ACTION_UP; } else { const MotionEventEntry& previousEntry = motions[i-1]; Loading Loading @@ -195,7 +196,7 @@ static std::vector<MotionEvent> createMotionEventStream( static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector<MotionEventEntry>& motions, int32_t axis, float targetVelocity) { float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); float Vx, Vy; Loading @@ -204,7 +205,7 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, vt.addMovement(&event); } vt.getVelocity(DEFAULT_POINTER_ID, &Vx, &Vy); vt.getVelocity(pointerId, &Vx, &Vy); switch (axis) { case AMOTION_EVENT_AXIS_X: Loading Loading @@ -846,12 +847,70 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi // Velocity should actually be zero, but we expect 0.016 here instead. // This is close enough to zero, and is likely caused by division by a very small number. computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, -0.016); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, -0.016); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, 0); } /** * ================= Pointer liftoff =============================================================== */ /** * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a short delay * between the last ACTION_MOVE and the next ACTION_POINTER_UP or ACTION_UP, velocity should not be * affected by the liftoff. */ TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { std::vector<MotionEventEntry> motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {30ms, {{30, 0}}}, // ACTION_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 1000); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 1000); } /** * The last movement of a single pointer is ACTION_UP. If there's a long delay between the last * ACTION_MOVE and the final ACTION_UP, velocity should be reported as zero because the pointer * should be assumed to have stopped. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { std::vector<MotionEventEntry> motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {3000ms, {{30, 0}}}, // ACTION_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); } /** * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a long delay * before ACTION_POINTER_UP event, the movement should be assumed to have stopped. * The final velocity should be reported as zero for all pointers. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { std::vector<MotionEventEntry> motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}, {100, 0}}}, {20ms, {{30, 0}, {200, 0}}}, {30ms, {{30, 0}, {300, 0}}}, {40ms, {{30, 0}, {400, 0}}}, {3000ms, {{30, 0}}}, // ACTION_POINTER_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 0); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 1); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 1); } /** * ================== Tests for least squares fitting ============================================== * Loading Loading
libs/input/VelocityTracker.cpp +56 −41 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ #include <utils/BitSet.h> #include <utils/Timers.h> using std::literals::chrono_literals::operator""ms; namespace android { /** Loading Loading @@ -57,8 +59,14 @@ static const nsecs_t NANOS_PER_MS = 1000000; // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the // velocity after the pointer starts moving again. static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms; static std::string toString(std::chrono::nanoseconds t) { std::stringstream stream; stream.precision(1); stream << std::fixed << std::chrono::duration<float, std::milli>(t).count() << " ms"; return stream.str(); } static float vectorDot(const float* a, const float* b, uint32_t m) { float r = 0; Loading Loading @@ -146,18 +154,14 @@ std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy( VelocityTracker::Strategy strategy) { switch (strategy) { case VelocityTracker::Strategy::IMPULSE: if (DEBUG_STRATEGY) { ALOGI("Initializing impulse strategy"); } ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy"); return std::make_unique<ImpulseVelocityTrackerStrategy>(); case VelocityTracker::Strategy::LSQ1: return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1); case VelocityTracker::Strategy::LSQ2: if (DEBUG_STRATEGY && !DEBUG_IMPULSE) { ALOGI("Initializing lsq2 strategy"); } ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy"); return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2); case VelocityTracker::Strategy::LSQ3: Loading Loading @@ -221,12 +225,11 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, idBits.clearLastMarkedBit(); } if ((mCurrentPointerIdBits.value & idBits.value) && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { if (DEBUG_VELOCITY) { ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", (eventTime - mLastEventTime) * 0.000001f); } if ((mCurrentPointerIdBits.value & idBits.value) && std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) { ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.", toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str()); // We have not received any movements for too long. Assume that all pointers // have stopped. mStrategy->clear(); Loading Loading @@ -281,8 +284,18 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: break; default: // Ignore all other actions because they do not convey any new information about case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_UP: { std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime); if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) { ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.", toString(delaySinceLastEvent).c_str()); // We have not received any movements for too long. Assume that all pointers // have stopped. mStrategy->clear(); } // These actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position // of the pointers that went up. ACTION_POINTER_UP does include the new position of Loading @@ -292,6 +305,10 @@ void VelocityTracker::addMovement(const MotionEvent* event) { // before adding the movement. return; } default: // Ignore all other actions. return; } size_t pointerCount = event->getPointerCount(); if (pointerCount > MAX_POINTERS) { Loading Loading @@ -438,10 +455,10 @@ void LeastSquaresVelocityTrackerStrategy::addMovement( static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y, const std::vector<float>& w, uint32_t n, float* outB, float* outDet) { const size_t m = x.size(); if (DEBUG_STRATEGY) { ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); } LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes"); // Expand the X vector to a matrix A, pre-multiplied by the weights. Loading @@ -452,9 +469,9 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo a[i][h] = a[i - 1][h] * x[h]; } } if (DEBUG_STRATEGY) { ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str()); } ALOGD_IF(DEBUG_STRATEGY, " - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str()); // Apply the Gram-Schmidt process to A to obtain its QR decomposition. float q[n][m]; // orthonormal basis, column-major order Loading @@ -473,9 +490,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo float norm = vectorNorm(&q[j][0], m); if (norm < 0.000001f) { // vectors are linearly dependent or zero so no solution if (DEBUG_STRATEGY) { ALOGD(" - no solution, norm=%f", norm); } ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm); return false; } Loading Loading @@ -518,9 +533,8 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo } outB[i] /= r[i][i]; } if (DEBUG_STRATEGY) { ALOGD(" - b=%s", vectorToString(outB, n).c_str()); } ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB, n).c_str()); // Calculate the coefficient of determination as 1 - (SSerr / SStot) where // SSerr is the residual sum of squares (variance of the error), Loading @@ -546,11 +560,11 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo sstot += w[h] * w[h] * var * var; } *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; if (DEBUG_STRATEGY) { ALOGD(" - sserr=%f", sserr); ALOGD(" - sstot=%f", sstot); ALOGD(" - det=%f", *outDet); } ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); return true; } Loading Loading @@ -673,11 +687,11 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; outEstimator->confidence = xdet * ydet; if (DEBUG_STRATEGY) { ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); } return true; } } Loading Loading @@ -1185,9 +1199,10 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; if (DEBUG_STRATEGY) { ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); } ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons Loading
libs/input/tests/VelocityTracker_test.cpp +66 −7 Original line number Diff line number Diff line Loading @@ -26,7 +26,9 @@ #include <gui/constants.h> #include <input/VelocityTracker.h> using namespace std::chrono_literals; using std::literals::chrono_literals::operator""ms; using std::literals::chrono_literals::operator""ns; using std::literals::chrono_literals::operator""us; using android::base::StringPrintf; namespace android { Loading Loading @@ -149,8 +151,7 @@ static std::vector<MotionEvent> createMotionEventStream( if (i == 0) { action = AMOTION_EVENT_ACTION_DOWN; EXPECT_EQ(1U, pointerCount) << "First event should only have 1 pointer"; } else if (i == motions.size() - 1) { EXPECT_EQ(1U, pointerCount) << "Last event should only have 1 pointer"; } else if ((i == motions.size() - 1) && pointerCount == 1) { action = AMOTION_EVENT_ACTION_UP; } else { const MotionEventEntry& previousEntry = motions[i-1]; Loading Loading @@ -195,7 +196,7 @@ static std::vector<MotionEvent> createMotionEventStream( static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector<MotionEventEntry>& motions, int32_t axis, float targetVelocity) { float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); float Vx, Vy; Loading @@ -204,7 +205,7 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, vt.addMovement(&event); } vt.getVelocity(DEFAULT_POINTER_ID, &Vx, &Vy); vt.getVelocity(pointerId, &Vx, &Vy); switch (axis) { case AMOTION_EVENT_AXIS_X: Loading Loading @@ -846,12 +847,70 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi // Velocity should actually be zero, but we expect 0.016 here instead. // This is close enough to zero, and is likely caused by division by a very small number. computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, -0.016); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, -0.016); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, 0); } /** * ================= Pointer liftoff =============================================================== */ /** * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a short delay * between the last ACTION_MOVE and the next ACTION_POINTER_UP or ACTION_UP, velocity should not be * affected by the liftoff. */ TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { std::vector<MotionEventEntry> motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {30ms, {{30, 0}}}, // ACTION_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 1000); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 1000); } /** * The last movement of a single pointer is ACTION_UP. If there's a long delay between the last * ACTION_MOVE and the final ACTION_UP, velocity should be reported as zero because the pointer * should be assumed to have stopped. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { std::vector<MotionEventEntry> motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {3000ms, {{30, 0}}}, // ACTION_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); } /** * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a long delay * before ACTION_POINTER_UP event, the movement should be assumed to have stopped. * The final velocity should be reported as zero for all pointers. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { std::vector<MotionEventEntry> motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}, {100, 0}}}, {20ms, {{30, 0}, {200, 0}}}, {30ms, {{30, 0}, {300, 0}}}, {40ms, {{30, 0}, {400, 0}}}, {3000ms, {{30, 0}}}, // ACTION_POINTER_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 0); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 1); computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, /*pointerId*/ 1); } /** * ================== Tests for least squares fitting ============================================== * Loading