Port the legacy velocity tracker strategy.

For comparison purposes, port the legacy velocity tracker
algorithm as it behaved prior to ICS.

Bug: 6413587
Change-Id: I7e8e56584dcdb1a3c660ca9d8f9c5bd5d868e449
diff --git a/include/androidfw/VelocityTracker.h b/include/androidfw/VelocityTracker.h
index 3e6436a..8c24219 100644
--- a/include/androidfw/VelocityTracker.h
+++ b/include/androidfw/VelocityTracker.h
@@ -225,6 +225,45 @@
     void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const;
 };
 
+
+/*
+ * Velocity tracker strategy used prior to ICS.
+ */
+class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+    LegacyVelocityTrackerStrategy();
+    virtual ~LegacyVelocityTrackerStrategy();
+
+    virtual void clear();
+    virtual void clearPointers(BitSet32 idBits);
+    virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+            const VelocityTracker::Position* positions);
+    virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+
+private:
+    // Oldest sample to consider when calculating the velocity.
+    static const nsecs_t HORIZON = 200 * 1000000; // 100 ms
+
+    // Number of samples to keep.
+    static const uint32_t HISTORY_SIZE = 20;
+
+    // The minimum duration between samples when estimating velocity.
+    static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
+
+    struct Movement {
+        nsecs_t eventTime;
+        BitSet32 idBits;
+        VelocityTracker::Position positions[MAX_POINTERS];
+
+        inline const VelocityTracker::Position& getPosition(uint32_t id) const {
+            return positions[idBits.getIndexOfBit(id)];
+        }
+    };
+
+    uint32_t mIndex;
+    Movement mMovements[HISTORY_SIZE];
+};
+
 } // namespace android
 
 #endif // _ANDROIDFW_VELOCITY_TRACKER_H
diff --git a/libs/androidfw/VelocityTracker.cpp b/libs/androidfw/VelocityTracker.cpp
index fc75576..f48ec62 100644
--- a/libs/androidfw/VelocityTracker.cpp
+++ b/libs/androidfw/VelocityTracker.cpp
@@ -190,6 +190,13 @@
         // for acceleration but it typically overestimates the effect.
         return new IntegratingVelocityTrackerStrategy(2);
     }
+    if (!strcmp("legacy", strategy)) {
+        // Legacy velocity tracker algorithm.  Quality: POOR.
+        // For comparison purposes only.  This algorithm is strongly influenced by
+        // old data points, consistently underestimates velocity and takes a very long
+        // time to adjust to changes in direction.
+        return new LegacyVelocityTrackerStrategy();
+    }
     return NULL;
 }
 
@@ -799,4 +806,123 @@
     outEstimator->yCoeff[2] = state.yaccel / 2;
 }
 
+
+// --- LegacyVelocityTrackerStrategy ---
+
+const nsecs_t LegacyVelocityTrackerStrategy::HORIZON;
+const uint32_t LegacyVelocityTrackerStrategy::HISTORY_SIZE;
+const nsecs_t LegacyVelocityTrackerStrategy::MIN_DURATION;
+
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {
+    clear();
+}
+
+LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
+}
+
+void LegacyVelocityTrackerStrategy::clear() {
+    mIndex = 0;
+    mMovements[0].idBits.clear();
+}
+
+void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
+    BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
+    mMovements[mIndex].idBits = remainingIdBits;
+}
+
+void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+        const VelocityTracker::Position* positions) {
+    if (++mIndex == HISTORY_SIZE) {
+        mIndex = 0;
+    }
+
+    Movement& movement = mMovements[mIndex];
+    movement.eventTime = eventTime;
+    movement.idBits = idBits;
+    uint32_t count = idBits.count();
+    for (uint32_t i = 0; i < count; i++) {
+        movement.positions[i] = positions[i];
+    }
+}
+
+bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
+        VelocityTracker::Estimator* outEstimator) const {
+    outEstimator->clear();
+
+    const Movement& newestMovement = mMovements[mIndex];
+    if (!newestMovement.idBits.hasBit(id)) {
+        return false; // no data
+    }
+
+    // Find the oldest sample that contains the pointer and that is not older than HORIZON.
+    nsecs_t minTime = newestMovement.eventTime - HORIZON;
+    uint32_t oldestIndex = mIndex;
+    uint32_t numTouches = 1;
+    do {
+        uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
+        const Movement& nextOldestMovement = mMovements[nextOldestIndex];
+        if (!nextOldestMovement.idBits.hasBit(id)
+                || nextOldestMovement.eventTime < minTime) {
+            break;
+        }
+        oldestIndex = nextOldestIndex;
+    } while (++numTouches < HISTORY_SIZE);
+
+    // 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 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 because
+    // the hardware or driver reports them irregularly or in bursts.
+    float accumVx = 0;
+    float accumVy = 0;
+    uint32_t index = oldestIndex;
+    uint32_t samplesUsed = 0;
+    const Movement& oldestMovement = mMovements[oldestIndex];
+    const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(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 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 VelocityTracker::Position& position = movement.getPosition(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 * lastDuration + vx * duration) / (duration + lastDuration);
+            accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
+            lastDuration = duration;
+            samplesUsed += 1;
+        }
+    }
+
+    // Report velocity.
+    const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id);
+    outEstimator->time = newestMovement.eventTime;
+    outEstimator->confidence = 1;
+    outEstimator->xCoeff[0] = newestPosition.x;
+    outEstimator->yCoeff[0] = newestPosition.y;
+    if (samplesUsed) {
+        outEstimator->xCoeff[1] = accumVx;
+        outEstimator->yCoeff[1] = accumVy;
+        outEstimator->degree = 1;
+    } else {
+        outEstimator->degree = 0;
+    }
+    return true;
+}
+
 } // namespace android