Merge "Ignore the closing MotionEvent of swipe gestures for falsing" into tm-qpr-dev
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index bc0f995..f83885b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -38,6 +38,7 @@
 public class FalsingDataProvider {
 
     private static final long MOTION_EVENT_AGE_MS = 1000;
+    private static final long DROP_EVENT_THRESHOLD_MS = 50;
     private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
 
     private final int mWidthPixels;
@@ -60,6 +61,7 @@
     private float mAngle = 0;
     private MotionEvent mFirstRecentMotionEvent;
     private MotionEvent mLastMotionEvent;
+    private boolean mDropLastEvent;
     private boolean mJustUnlockedWithFace;
     private boolean mA11YAction;
 
@@ -95,6 +97,12 @@
             // Ensure prior gesture was completed. May be a no-op.
             completePriorGesture();
         }
+
+        // Drop the gesture closing event if it is close in time to a previous ACTION_MOVE event.
+        // The reason is that the closing ACTION_UP event of  a swipe can be a bit offseted from the
+        // previous ACTION_MOVE event and when it happens, it makes some classifiers fail.
+        mDropLastEvent = shouldDropEvent(motionEvent);
+
         mRecentMotionEvents.addAll(motionEvents);
 
         FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size());
@@ -129,6 +137,7 @@
             mPriorMotionEvents = mRecentMotionEvents;
             mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
         }
+        mDropLastEvent = false;
         mA11YAction = false;
     }
 
@@ -150,8 +159,18 @@
         return mYdpi;
     }
 
+    /**
+     * Get the {@link MotionEvent}s of the most recent gesture.
+     *
+     * Note that this list may not include the last recorded event.
+     * @see #mDropLastEvent
+     */
     public List<MotionEvent> getRecentMotionEvents() {
-        return mRecentMotionEvents;
+        if (!mDropLastEvent || mRecentMotionEvents.isEmpty()) {
+            return mRecentMotionEvents;
+        } else {
+            return mRecentMotionEvents.subList(0, mRecentMotionEvents.size() - 1);
+        }
     }
 
     public List<MotionEvent> getPriorMotionEvents() {
@@ -169,7 +188,12 @@
         return mFirstRecentMotionEvent;
     }
 
-    /** Get the last recorded {@link MotionEvent}. */
+    /**
+     * Get the last {@link MotionEvent} of the most recent gesture.
+     *
+     * Note that this may be the event prior to the last recorded event.
+     * @see #mDropLastEvent
+     */
     public MotionEvent getLastMotionEvent() {
         recalculateData();
         return mLastMotionEvent;
@@ -236,12 +260,13 @@
             return;
         }
 
-        if (mRecentMotionEvents.isEmpty()) {
+        List<MotionEvent> recentMotionEvents = getRecentMotionEvents();
+        if (recentMotionEvents.isEmpty()) {
             mFirstRecentMotionEvent = null;
             mLastMotionEvent = null;
         } else {
-            mFirstRecentMotionEvent = mRecentMotionEvents.get(0);
-            mLastMotionEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1);
+            mFirstRecentMotionEvent = recentMotionEvents.get(0);
+            mLastMotionEvent = recentMotionEvents.get(recentMotionEvents.size() - 1);
         }
 
         calculateAngleInternal();
@@ -249,6 +274,17 @@
         mDirty = false;
     }
 
+    private boolean shouldDropEvent(MotionEvent event) {
+        if (mRecentMotionEvents.size() < 3) return false;
+
+        MotionEvent lastEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1);
+        boolean isCompletingGesture = event.getActionMasked() == MotionEvent.ACTION_UP
+                && lastEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
+        boolean isRecentEvent =
+                event.getEventTime() - lastEvent.getEventTime() < DROP_EVENT_THRESHOLD_MS;
+        return isCompletingGesture && isRecentEvent;
+    }
+
     private void calculateAngleInternal() {
         if (mRecentMotionEvents.size() < 2) {
             mAngle = Float.MAX_VALUE;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
index e5da389..addd8e2 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
@@ -183,7 +183,7 @@
 
     @Override
     public List<MotionEvent> subList(int fromIndex, int toIndex) {
-        throw new UnsupportedOperationException();
+        return mMotionEvents.subList(fromIndex, toIndex);
     }
 
     class Iter implements ListIterator<MotionEvent> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
index faa5db4..ab6d5b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
@@ -94,7 +94,9 @@
         mClassifier.onTouchEvent(appendMoveEvent(1, 16, 3));
         mClassifier.onTouchEvent(appendMoveEvent(1, 17, 300));
         mClassifier.onTouchEvent(appendMoveEvent(1, 18, 301));
-        mClassifier.onTouchEvent(appendUpEvent(1, 19, 501));
+        mClassifier.onTouchEvent(appendMoveEvent(1, 19, 501));
+        mClassifier.onTouchEvent(appendUpEvent(1, 19, 501)); //event will be dropped
+
         assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 2edc3d3..8eadadf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -75,16 +75,17 @@
     }
 
     @Test
-    public void test_trackMotionEvents() {
+    public void test_trackMotionEvents_dropUpEvent() {
         mDataProvider.onMotionEvent(appendDownEvent(2, 9));
         mDataProvider.onMotionEvent(appendMoveEvent(4, 7));
-        mDataProvider.onMotionEvent(appendUpEvent(6, 5));
+        mDataProvider.onMotionEvent(appendMoveEvent(6, 5));
+        mDataProvider.onMotionEvent(appendUpEvent(0, 0)); // event will be dropped
         List<MotionEvent> motionEventList = mDataProvider.getRecentMotionEvents();
 
         assertThat(motionEventList.size()).isEqualTo(3);
         assertThat(motionEventList.get(0).getActionMasked()).isEqualTo(MotionEvent.ACTION_DOWN);
         assertThat(motionEventList.get(1).getActionMasked()).isEqualTo(MotionEvent.ACTION_MOVE);
-        assertThat(motionEventList.get(2).getActionMasked()).isEqualTo(MotionEvent.ACTION_UP);
+        assertThat(motionEventList.get(2).getActionMasked()).isEqualTo(MotionEvent.ACTION_MOVE);
         assertThat(motionEventList.get(0).getEventTime()).isEqualTo(1L);
         assertThat(motionEventList.get(1).getEventTime()).isEqualTo(2L);
         assertThat(motionEventList.get(2).getEventTime()).isEqualTo(3L);
@@ -97,6 +98,28 @@
     }
 
     @Test
+    public void test_trackMotionEvents_keepUpEvent() {
+        mDataProvider.onMotionEvent(appendDownEvent(2, 9));
+        mDataProvider.onMotionEvent(appendMoveEvent(4, 7));
+        mDataProvider.onMotionEvent(appendUpEvent(0, 0, 100));
+        List<MotionEvent> motionEventList = mDataProvider.getRecentMotionEvents();
+
+        assertThat(motionEventList.size()).isEqualTo(3);
+        assertThat(motionEventList.get(0).getActionMasked()).isEqualTo(MotionEvent.ACTION_DOWN);
+        assertThat(motionEventList.get(1).getActionMasked()).isEqualTo(MotionEvent.ACTION_MOVE);
+        assertThat(motionEventList.get(2).getActionMasked()).isEqualTo(MotionEvent.ACTION_UP);
+        assertThat(motionEventList.get(0).getEventTime()).isEqualTo(1L);
+        assertThat(motionEventList.get(1).getEventTime()).isEqualTo(2L);
+        assertThat(motionEventList.get(2).getEventTime()).isEqualTo(100);
+        assertThat(motionEventList.get(0).getX()).isEqualTo(2f);
+        assertThat(motionEventList.get(1).getX()).isEqualTo(4f);
+        assertThat(motionEventList.get(2).getX()).isEqualTo(0f);
+        assertThat(motionEventList.get(0).getY()).isEqualTo(9f);
+        assertThat(motionEventList.get(1).getY()).isEqualTo(7f);
+        assertThat(motionEventList.get(2).getY()).isEqualTo(0f);
+    }
+
+    @Test
     public void test_trackRecentMotionEvents() {
         mDataProvider.onMotionEvent(appendDownEvent(2, 9, 1));
         mDataProvider.onMotionEvent(appendMoveEvent(4, 7, 800));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
index c343c20..ae2b8bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
@@ -68,6 +68,15 @@
     }
 
     @Test
+    public void testPass_dropClosingUpEvent() {
+        appendMoveEvent(0, 0);
+        appendMoveEvent(0, 100);
+        appendMoveEvent(0, 200);
+        appendUpEvent(0, 180); // this event would push us over the maxDevianceY
+        assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
+    }
+
+    @Test
     public void testPass_fewTouchesHorizontal() {
         assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
         appendMoveEvent(0, 0);