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

Commit 689e53cc authored by Jeff Brown's avatar Jeff Brown Committed by Android Git Automerger
Browse files

am 16330e24: am 94e838f6: Merge "Improve VelocityTracker numerical stability....

am 16330e24: am 94e838f6: Merge "Improve VelocityTracker numerical stability. (DO NOT MERGE)" into honeycomb-mr2

* commit '16330e249663fed890df0e95fce4016c2971120a':
  Improve VelocityTracker numerical stability. (DO NOT MERGE)
parents 446b4009 8cfb70cd
Loading
Loading
Loading
Loading
+26 −3
Original line number Diff line number Diff line
@@ -313,6 +313,13 @@ public:

    inline int32_t getAction() const { return mAction; }

    inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }

    inline int32_t getActionIndex() const {
        return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
                >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
    }

    inline void setAction(int32_t action) { mAction = action; }

    inline int32_t getFlags() const { return mFlags; }
@@ -460,6 +467,8 @@ public:
                AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
    }

    ssize_t findPointerIndex(int32_t pointerId) const;

    void initialize(
            int32_t deviceId,
            int32_t source,
@@ -553,8 +562,7 @@ private:
};

/*
 * Calculates the velocity of pointer motions over time.
 * Uses essentially the same algorithm as android.view.VelocityTracker.
 * Calculates the velocity of pointer movements over time.
 */
class VelocityTracker {
public:
@@ -567,6 +575,11 @@ public:
    // Resets the velocity tracker state.
    void clear();

    // Resets the velocity tracker state for specific pointers.
    // Call this method when some pointers have changed and may be reusing
    // an id that was assigned to a different pointer earlier.
    void clearPointers(BitSet32 idBits);

    // Adds movement information for a set of pointers.
    // The idBits bitfield specifies the pointer ids of the pointers whose positions
    // are included in the movement.
@@ -574,11 +587,20 @@ public:
    // increasing id.  Its size should be equal to the number of one bits in idBits.
    void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions);

    // Adds movement information for all pointers in a MotionEvent, including historical samples.
    void addMovement(const MotionEvent* event);

    // Gets the velocity of the specified pointer id in position units per second.
    // Returns false and sets the velocity components to zero if there is no movement
    // information for the pointer.
    bool getVelocity(uint32_t id, float* outVx, float* outVy) const;

    // Gets the active pointer id, or -1 if none.
    inline int32_t getActivePointerId() const { return mActivePointerId; }

    // Gets a bitset containing all pointer ids from the most recent movement.
    inline BitSet32 getCurrentPointerIdBits() const { return mMovements[mIndex].idBits; }

private:
    // Number of samples to keep.
    static const uint32_t HISTORY_SIZE = 10;
@@ -587,7 +609,7 @@ private:
    static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms

    // The minimum duration between samples when estimating velocity.
    static const nsecs_t MIN_DURATION = 5 * 1000000; // 5 ms
    static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms

    struct Movement {
        nsecs_t eventTime;
@@ -597,6 +619,7 @@ private:

    uint32_t mIndex;
    Movement mMovements[HISTORY_SIZE];
    int32_t mActivePointerId;
};

/*
+4 −0
Original line number Diff line number Diff line
@@ -61,6 +61,10 @@ struct BitSet32 {
    // Result is undefined if all bits are marked.
    inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); }

    // Finds the last marked bit in the set.
    // Result is undefined if all bits are unmarked.
    inline uint32_t lastMarkedBit() const { return 31 - __builtin_ctz(value); }

    // Gets the index of the specified bit in the set, which is the number of
    // marked bits that appear before the specified bit.
    inline uint32_t getIndexOfBit(uint32_t n) const {
+115 −32
Original line number Diff line number Diff line
@@ -483,6 +483,16 @@ float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
    return value;
}

ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
    size_t pointerCount = mPointerIds.size();
    for (size_t i = 0; i < pointerCount; i++) {
        if (mPointerIds.itemAt(i) == pointerId) {
            return i;
        }
    }
    return -1;
}

void MotionEvent::offsetLocation(float xOffset, float yOffset) {
    mXOffset += xOffset;
    mYOffset += yOffset;
@@ -667,12 +677,27 @@ VelocityTracker::VelocityTracker() {
void VelocityTracker::clear() {
    mIndex = 0;
    mMovements[0].idBits.clear();
    mActivePointerId = -1;
}

void VelocityTracker::clearPointers(BitSet32 idBits) {
    BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
    mMovements[mIndex].idBits = remainingIdBits;

    if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
        mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
    }
}

void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
    if (++mIndex == HISTORY_SIZE) {
        mIndex = 0;
    }

    while (idBits.count() > MAX_POINTERS) {
        idBits.clearBit(idBits.lastMarkedBit());
    }

    Movement& movement = mMovements[mIndex];
    movement.eventTime = eventTime;
    movement.idBits = idBits;
@@ -681,8 +706,13 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
        movement.positions[i] = positions[i];
    }

    if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
        mActivePointerId = count != 0 ? idBits.firstMarkedBit() : -1;
    }

#if DEBUG_VELOCITY
    LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x", eventTime, idBits.value);
    LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d",
            eventTime, idBits.value, mActivePointerId);
    for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
        uint32_t id = iterBits.firstMarkedBit();
        uint32_t index = idBits.getIndexOfBit(id);
@@ -690,7 +720,7 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
        float vx, vy;
        bool available = getVelocity(id, &vx, &vy);
        if (available) {
            LOGD("  %d: position (%0.3f, %0.3f), velocity (%0.3f, %0.3f), speed %0.3f",
            LOGD("  %d: position (%0.3f, %0.3f), vx=%0.3f, vy=%0.3f, speed=%0.3f",
                    id, positions[index].x, positions[index].y, vx, vy, sqrtf(vx * vx + vy * vy));
        } else {
            assert(vx == 0 && vy == 0);
@@ -701,6 +731,70 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
#endif
}

void VelocityTracker::addMovement(const MotionEvent* event) {
    int32_t actionMasked = event->getActionMasked();

    switch (actionMasked) {
    case AMOTION_EVENT_ACTION_DOWN:
        // Clear all pointers on down before adding the new movement.
        clear();
        break;
    case AMOTION_EVENT_ACTION_POINTER_DOWN: {
        // Start a new movement trace for a pointer that just went down.
        // We do this on down instead of on up because the client may want to query the
        // final velocity for a pointer that just went up.
        BitSet32 downIdBits;
        downIdBits.markBit(event->getActionIndex());
        clearPointers(downIdBits);
        break;
    }
    case AMOTION_EVENT_ACTION_OUTSIDE:
    case AMOTION_EVENT_ACTION_CANCEL:
    case AMOTION_EVENT_ACTION_SCROLL:
    case AMOTION_EVENT_ACTION_UP:
    case AMOTION_EVENT_ACTION_POINTER_UP:
        // Ignore 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
        // pointers that remained down but we will also receive an ACTION_MOVE with this
        // information if any of them actually moved.  Since we don't know how many pointers
        // will be going up at once it makes sense to just wait for the following ACTION_MOVE
        // before adding the movement.
        return;
    }

    size_t pointerCount = event->getPointerCount();
    if (pointerCount > MAX_POINTERS) {
        pointerCount = MAX_POINTERS;
    }

    BitSet32 idBits;
    for (size_t i = 0; i < pointerCount; i++) {
        idBits.markBit(event->getPointerId(i));
    }

    nsecs_t eventTime;
    Position positions[pointerCount];

    size_t historySize = event->getHistorySize();
    for (size_t h = 0; h < historySize; h++) {
        eventTime = event->getHistoricalEventTime(h);
        for (size_t i = 0; i < pointerCount; i++) {
            positions[i].x = event->getHistoricalX(i, h);
            positions[i].y = event->getHistoricalY(i, h);
        }
        addMovement(eventTime, idBits, positions);
    }

    eventTime = event->getEventTime();
    for (size_t i = 0; i < pointerCount; i++) {
        positions[i].x = event->getX(i);
        positions[i].y = event->getY(i);
    }
    addMovement(eventTime, idBits, positions);
}

bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
    const Movement& newestMovement = mMovements[mIndex];
    if (newestMovement.idBits.hasBit(id)) {
@@ -718,36 +812,17 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
            oldestIndex = nextOldestIndex;
        } while (++numTouches < HISTORY_SIZE);

        // If we have a lot of samples, skip the last received sample since it is
        // probably pretty noisy compared to the sum of all of the traces already acquired.
        //
        // NOTE: This condition exists in the android.view.VelocityTracker and imposes a
        // bias against the most recent data.
        if (numTouches > 3) {
            numTouches -= 1;
        }

        // Calculate an exponentially weighted moving average of the velocity at different
        // points in time measured relative to the oldest samples.  This is essentially
        // an IIR filter.
        // Calculate an exponentially weighted moving average of the velocity estimate
        // at different points in time measured relative to the oldest sample.
        // This is essentially an IIR filter.  Newer samples are weighted more heavily
        // than older samples.  Samples at equal time points are weighted more or less
        // equally.
        //
        // One problem with this algorithm is that the sample data may be poorly conditioned.
        // One tricky problem is that the sample data may be poorly conditioned.
        // Sometimes samples arrive very close together in time which can cause us to
        // overestimate the velocity at that time point.  Most samples might be measured
        // 16ms apart but some consecutive samples could be only 0.5sm apart due to
        // the way they are reported by the hardware or driver (sometimes in bursts or with
        // significant jitter).  The instantaneous velocity for those samples 0.5ms apart will
        // be calculated to be 32 times what it should have been.
        // To work around this effect, we impose a minimum duration on the samples.
        //
        // FIXME: Samples close together in time can have an disproportionately large
        // impact on the result because all samples are equally weighted.  The average should
        // instead take the time factor into account.
        //
        // FIXME: The minimum duration condition does not exist in
        // android.view.VelocityTracker yet.  It is less important there because sample times
        // are truncated to the millisecond so back to back samples will often appear to be
        // zero milliseconds apart and will be ignored if they are the oldest ones.
        // 16ms apart but some consecutive samples could be only 0.5sm apart because
        // the hardware or driver reports them irregularly or in bursts.
        float accumVx = 0;
        float accumVy = 0;
        uint32_t index = oldestIndex;
@@ -755,19 +830,27 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
        const Movement& oldestMovement = mMovements[oldestIndex];
        const Position& oldestPosition =
                oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
        nsecs_t lastDuration = 0;
        while (numTouches-- > 1) {
            if (++index == HISTORY_SIZE) {
                index = 0;
            }
            const Movement& movement = mMovements[index];
            nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
            if (duration > MIN_DURATION) {

            // If the duration between samples is small, we may significantly overestimate
            // the velocity.  Consequently, we impose a minimum duration constraint on the
            // samples that we include in the calculation.
            if (duration >= MIN_DURATION) {
                const Position& position = movement.positions[movement.idBits.getIndexOfBit(id)];
                float scale = 1000000000.0f / duration; // one over time delta in seconds
                float vx = (position.x - oldestPosition.x) * scale;
                float vy = (position.y - oldestPosition.y) * scale;
                accumVx = accumVx == 0 ? vx : (accumVx + vx) * 0.5f;
                accumVy = accumVy == 0 ? vy : (accumVy + vy) * 0.5f;

                accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
                accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);

                lastDuration = duration;
                samplesUsed += 1;
            }
        }