Reduce false swipe-closed gestures in status bar panels.

VelocityTracker is a powerful tool---too powerful, in this
case, because many devices (I'm looking at you, Nexus 7)
have touchscreens that report total garbage on takeoff and
landing.

PanelView now uses its own incredibly crude velocity tracker
called FlingTracker, which implements a short sliding window
of the last 8 motion events (7 intervals) over which touch
velocity is averaged. There's also a little bias toward more
recent touch events so that the overall velocity of small
circular gestures will tend to favor the exit tangent.

The end result is a primitive low-pass filter on touch
velocity that should help us avoid situations where one (or
even two!) stray MotionEvents at the end of a gesture won't
invalidate the overall thrust.

Bug: 7422342
Change-Id: Idae38d1957727e400493324af4eee357ba5baa27
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 7035006..5d9c7bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.phone;
 
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Iterator;
+
 import android.animation.ObjectAnimator;
 import android.animation.TimeAnimator;
 import android.animation.TimeAnimator.TimeListener;
@@ -73,7 +77,93 @@
 
     private TimeAnimator mTimeAnimator;
     private ObjectAnimator mPeekAnimator;
-    private VelocityTracker mVelocityTracker;
+    private FlingTracker mVelocityTracker;
+
+    /**
+     * A very simple low-pass velocity filter for motion events; not nearly as sophisticated as
+     * VelocityTracker but optimized for the kinds of gestures we expect to see in status bar
+     * panels.
+     */
+    private static class FlingTracker {
+        static final boolean DEBUG = false;
+        final int MAX_EVENTS = 8;
+        final float DECAY = 0.75f;
+        ArrayDeque<MotionEventCopy> mEventBuf = new ArrayDeque<MotionEventCopy>(MAX_EVENTS);
+        float mVX, mVY = 0;
+        private static class MotionEventCopy {
+            public MotionEventCopy(float x2, float y2, long eventTime) {
+                this.x = x2;
+                this.y = y2;
+                this.t = eventTime;
+            }
+            public float x, y;
+            public long t;
+        }
+        public FlingTracker() {
+        }
+        public void addMovement(MotionEvent event) {
+            if (mEventBuf.size() == MAX_EVENTS) {
+                mEventBuf.remove();
+            }
+            mEventBuf.add(new MotionEventCopy(event.getX(), event.getY(), event.getEventTime()));
+        }
+        public void computeCurrentVelocity(long timebase) {
+            if (FlingTracker.DEBUG) {
+                Slog.v("FlingTracker", "computing velocities for " + mEventBuf.size() + " events");
+            }
+            mVX = mVY = 0;
+            MotionEventCopy last = null;
+            int i = 0;
+            float totalweight = 0f;
+            float weight = 10f;
+            for (final Iterator<MotionEventCopy> iter = mEventBuf.descendingIterator();
+                    iter.hasNext();) {
+                final MotionEventCopy event = iter.next();
+                if (last != null) {
+                    final float dt = (float) (event.t - last.t) / timebase;
+                    final float dx = (event.x - last.x);
+                    final float dy = (event.y - last.y);
+                    if (FlingTracker.DEBUG) {
+                        Slog.v("FlingTracker", String.format("   [%d] dx=%.1f dy=%.1f dt=%.0f vx=%.1f vy=%.1f",
+                                i,
+                                dx, dy, dt,
+                                (dx/dt),
+                                (dy/dt)
+                                ));
+                    }
+                    mVX += weight * dx / dt;
+                    mVY += weight * dy / dt;
+                    totalweight += weight;
+                    weight *= DECAY;
+                }
+                last = event;
+                i++;
+            }
+            mVX /= totalweight;
+            mVY /= totalweight;
+
+            if (FlingTracker.DEBUG) {
+                Slog.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
+            }
+        }
+        public float getXVelocity() {
+            return mVX;
+        }
+        public float getYVelocity() {
+            return mVY;
+        }
+        public void recycle() {
+            mEventBuf.clear();
+        }
+
+        static FlingTracker sTracker;
+        static FlingTracker obtain() {
+            if (sTracker == null) {
+                sTracker = new FlingTracker();
+            }
+            return sTracker;
+        }
+    }
 
     private int[] mAbsPos = new int[2];
     PanelBar mBar;
@@ -268,7 +358,7 @@
                             mHandleView.setPressed(true);
                             postInvalidate(); // catch the press state change
                             mInitialTouchY = y;
-                            mVelocityTracker = VelocityTracker.obtain();
+                            mVelocityTracker = FlingTracker.obtain();
                             trackMovement(event);
                             mTimeAnimator.cancel(); // end any outstanding animations
                             mBar.onTrackingStarted(PanelView.this);