Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 55b2c0b5 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Check whether pointer has stopped at liftoff"

parents 715b4413 456acaae
Loading
Loading
Loading
Loading
+56 −41
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@
#include <utils/BitSet.h>
#include <utils/Timers.h>

using std::literals::chrono_literals::operator""ms;

namespace android {

/**
@@ -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;
@@ -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:
@@ -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();
@@ -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
@@ -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) {
@@ -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.
@@ -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
@@ -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;
        }

@@ -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),
@@ -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;
}

@@ -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;
        }
    }
@@ -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
+66 −7
Original line number Diff line number Diff line
@@ -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 {
@@ -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];
@@ -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;

@@ -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:
@@ -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 ==============================================
 *