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