Merge "Using LeastSquaresVelocityTrackerStrategy for calculating velocity when detecting motion pause events. This is the same logic used by the platform implementation VelocityTracker" into ub-launcher3-master
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index d8b10b6..7d52571 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LSQ_VELOCITY_PROVIDER;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.MotionEvent;
@@ -85,7 +87,8 @@
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
-        mVelocityProvider = new LinearVelocityProvider(axis);
+        mVelocityProvider = ENABLE_LSQ_VELOCITY_PROVIDER.get()
+                ? new LSqVelocityProvider(axis) : new LinearVelocityProvider(axis);
     }
 
     /**
@@ -106,8 +109,6 @@
     /**
      * Computes velocity and acceleration to determine whether the motion is paused.
      * @param ev The motion being tracked.
-     *
-     * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
      */
     public void addPosition(MotionEvent ev) {
         addPosition(ev, 0);
@@ -248,4 +249,137 @@
             mPreviousPosition = null;
         }
     }
+
+    /**
+     * Java implementation of {@link android.view.VelocityTracker} using the Least Square (deg 2)
+     * algorithm.
+     */
+    private static class LSqVelocityProvider implements VelocityProvider {
+
+        // Maximum age of a motion event to be considered when calculating the velocity.
+        private static final long HORIZON_MS = 100;
+        // Number of samples to keep.
+        private static final int HISTORY_SIZE = 20;
+
+        // Position history are stored in a circular array
+        private final float[] mHistoricTimes = new float[HISTORY_SIZE];
+        private final float[] mHistoricPos = new float[HISTORY_SIZE];
+        private int mHistoryCount = 0;
+        private int mHistoryStart = 0;
+
+        private final int mAxis;
+
+        LSqVelocityProvider(int axis) {
+            mAxis = axis;
+        }
+
+        @Override
+        public void clear() {
+            mHistoryCount = mHistoryStart = 0;
+        }
+
+        private void addPositionAndTime(float eventTime, float eventPosition) {
+            mHistoricTimes[mHistoryStart] = eventTime;
+            mHistoricPos[mHistoryStart] = eventPosition;
+            mHistoryStart++;
+            if (mHistoryStart >= HISTORY_SIZE) {
+                mHistoryStart = 0;
+            }
+            mHistoryCount = Math.min(HISTORY_SIZE, mHistoryCount + 1);
+        }
+
+        @Override
+        public Float addMotionEvent(MotionEvent ev, int pointer) {
+            // Add all historic points
+            int historyCount = ev.getHistorySize();
+            for (int i = 0; i < historyCount; i++) {
+                addPositionAndTime(
+                        ev.getHistoricalEventTime(i), ev.getHistoricalAxisValue(mAxis, pointer, i));
+            }
+
+            // Start index for the last position (about to be added)
+            int eventStartIndex = mHistoryStart;
+            addPositionAndTime(ev.getEventTime(), ev.getAxisValue(mAxis, pointer));
+            return solveUnweightedLeastSquaresDeg2(eventStartIndex);
+        }
+
+        /**
+         * Solves the instantaneous velocity.
+         * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp
+         */
+        private Float solveUnweightedLeastSquaresDeg2(final int pointPos) {
+            final float eventTime = mHistoricTimes[pointPos];
+
+            float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
+            int count = 0;
+            for (int i = 0; i < mHistoryCount; i++) {
+                int index = pointPos - i;
+                if (index < 0) {
+                    index += HISTORY_SIZE;
+                }
+
+                float time = mHistoricTimes[index];
+                float age = eventTime - time;
+                if (age > HORIZON_MS) {
+                    break;
+                }
+                count++;
+                float xi = -age;
+
+                float yi = mHistoricPos[index];
+                float xi2 = xi * xi;
+                float xi3 = xi2 * xi;
+                float xi4 = xi3 * xi;
+                float xiyi = xi * yi;
+                float xi2yi = xi2 * yi;
+
+                sxi += xi;
+                sxi2 += xi2;
+                sxiyi += xiyi;
+                sxi2yi += xi2yi;
+                syi += yi;
+                sxi3 += xi3;
+                sxi4 += xi4;
+            }
+
+            if (count < 3) {
+                // Too few samples
+                if (count == 2) {
+                    int endPos = pointPos - 1;
+                    if (endPos < 0) {
+                        endPos += HISTORY_SIZE;
+                    }
+                    float denominator = eventTime - mHistoricTimes[endPos];
+                    if (denominator != 0) {
+                        return (eventTime - mHistoricPos[endPos]) / denominator;
+
+                    }
+                }
+                return null;
+            }
+
+            float Sxx = sxi2 - sxi * sxi / count;
+            float Sxy = sxiyi - sxi * syi / count;
+            float Sxx2 = sxi3 - sxi * sxi2 / count;
+            float Sx2y = sxi2yi - sxi2 * syi / count;
+            float Sx2x2 = sxi4 - sxi2 * sxi2 / count;
+
+            float denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
+            if (denominator == 0) {
+                // division by 0 when computing velocity
+                return null;
+            }
+            // Compute a
+            // float numerator = Sx2y*Sxx - Sxy*Sxx2;
+
+            // Compute b
+            float numerator = Sxy * Sx2x2 - Sx2y * Sxx2;
+            float b = numerator / denominator;
+
+            // Compute c
+            // float c = syi/count - b * sxi/count - a * sxi2/count;
+
+            return b;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6413044..3d8a9d7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -135,6 +135,10 @@
             "ENABLE_UNIVERSAL_SMARTSPACE", false,
             "Replace Smartspace with a version rendered by System UI.");
 
+    public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
+            "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+            "Use Least Square algorithm for motion pause detection.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {