Tests for RSB stretch on scrolling views

Bug: 204794759

Tests for RSB stretch overscroll for ListView,
ScrollView, and HorizontalScrollView.

Test: These are the tests
Change-Id: I72796dc0ea6ff7915e09669761177b10677ca34a
diff --git a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
index 0067a8a..3f62731 100644
--- a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
@@ -36,6 +36,8 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Xml;
+import android.view.InputDevice;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
@@ -949,6 +951,108 @@
                 edgeEffect.onAbsorbVelocity > 0);
     }
 
+    @Test
+    public void scrollFromRotaryStretchesLeft() throws Throwable {
+        showOnlyStretch();
+
+        ScrollViewTest.CaptureOnReleaseEdgeEffect
+                edgeEffect = new ScrollViewTest.CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowLeft = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(-2f, InputDevice.SOURCE_ROTARY_ENCODER)));
+            assertFalse(edgeEffect.isFinished());
+            assertTrue(edgeEffect.getDistance() > 0f);
+            assertTrue(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromMouseDoesNotStretchLeft() throws Throwable {
+        showOnlyStretch();
+
+        ScrollViewTest.CaptureOnReleaseEdgeEffect
+                edgeEffect = new ScrollViewTest.CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowLeft = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(-2f, InputDevice.SOURCE_MOUSE)));
+            assertTrue(edgeEffect.isFinished());
+            assertFalse(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromRotaryStretchesRight() throws Throwable {
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the Right
+            mScrollViewStretch.scrollTo(210, 0);
+        });
+
+        ScrollViewTest.CaptureOnReleaseEdgeEffect
+                edgeEffect = new ScrollViewTest.CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowRight = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(2f, InputDevice.SOURCE_ROTARY_ENCODER)));
+            assertFalse(edgeEffect.isFinished());
+            assertTrue(edgeEffect.getDistance() > 0f);
+            assertTrue(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromMouseDoesNotStretchRight() throws Throwable {
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the Right
+            mScrollViewStretch.scrollTo(210, 0);
+        });
+
+        ScrollViewTest.CaptureOnReleaseEdgeEffect
+                edgeEffect = new ScrollViewTest.CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowRight = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(2f, InputDevice.SOURCE_MOUSE)));
+            assertTrue(edgeEffect.isFinished());
+            assertFalse(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    private MotionEvent createScrollEvent(float scrollAmount, int source) {
+        MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+        pointerProperties.toolType = MotionEvent.TOOL_TYPE_MOUSE;
+        MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+        int axis = source == InputDevice.SOURCE_ROTARY_ENCODER ? MotionEvent.AXIS_SCROLL
+                : MotionEvent.AXIS_HSCROLL;
+        pointerCoords.setAxisValue(axis, scrollAmount);
+
+        return MotionEvent.obtain(
+                0, /* downTime */
+                0, /* eventTime */
+                MotionEvent.ACTION_SCROLL, /* action */
+                1, /* pointerCount */
+                new MotionEvent.PointerProperties[] { pointerProperties },
+                new MotionEvent.PointerCoords[] { pointerCoords },
+                0, /* metaState */
+                0, /* buttonState */
+                0f, /* xPrecision */
+                0f, /* yPrecision */
+                0, /* deviceId */
+                0, /* edgeFlags */
+                source, /* source */
+                0 /* flags */
+        );
+    }
+
     private void showOnlyStretch() throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             mScrollViewCustom.setVisibility(View.GONE);
diff --git a/tests/tests/widget/src/android/widget/cts/ListViewTest.java b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
index 6cf81e3..9abef33 100644
--- a/tests/tests/widget/src/android/widget/cts/ListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
@@ -56,7 +56,9 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.Xml;
+import android.view.InputDevice;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.LayoutAnimationController;
@@ -65,6 +67,7 @@
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.BaseAdapter;
+import android.widget.EdgeEffect;
 import android.widget.FrameLayout;
 import android.widget.ListView;
 import android.widget.TextView;
@@ -1413,6 +1416,102 @@
         assertNotEquals(firstVisible, mListViewStretch.getFirstVisiblePosition());
     }
 
+    @Test
+    public void scrollFromRotaryStretchesTop() throws Throwable {
+        showOnlyStretch();
+
+        CaptureOnReleaseEdgeEffect
+                edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mListViewStretch.mEdgeGlowTop = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(mListViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(2f, InputDevice.SOURCE_ROTARY_ENCODER)));
+            assertFalse(edgeEffect.isFinished());
+            assertTrue(edgeEffect.getDistance() > 0f);
+            assertTrue(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromMouseDoesNotStretchTop() throws Throwable {
+        showOnlyStretch();
+
+        CaptureOnReleaseEdgeEffect
+                edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mListViewStretch.mEdgeGlowTop = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mListViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(2f, InputDevice.SOURCE_MOUSE)));
+            assertTrue(edgeEffect.isFinished());
+            assertFalse(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromRotaryStretchesBottom() throws Throwable {
+        showOnlyStretch();
+
+        scrollToBottomOfStretch();
+
+        CaptureOnReleaseEdgeEffect
+                edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mListViewStretch.mEdgeGlowBottom = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(mListViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(-2f, InputDevice.SOURCE_ROTARY_ENCODER)));
+            assertFalse(edgeEffect.isFinished());
+            assertTrue(edgeEffect.getDistance() > 0f);
+            assertTrue(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromMouseDoesNotStretchBottom() throws Throwable {
+        showOnlyStretch();
+
+        scrollToBottomOfStretch();
+
+        CaptureOnReleaseEdgeEffect
+                edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mListViewStretch.mEdgeGlowBottom = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mListViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(-2f, InputDevice.SOURCE_MOUSE)));
+            assertTrue(edgeEffect.isFinished());
+            assertFalse(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    private MotionEvent createScrollEvent(float scrollAmount, int source) {
+        MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+        pointerProperties.toolType = MotionEvent.TOOL_TYPE_MOUSE;
+        MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+        int axis = source == InputDevice.SOURCE_ROTARY_ENCODER ? MotionEvent.AXIS_SCROLL
+                : MotionEvent.AXIS_VSCROLL;
+        pointerCoords.setAxisValue(axis, scrollAmount);
+
+        return MotionEvent.obtain(
+                0, /* downTime */
+                0, /* eventTime */
+                MotionEvent.ACTION_SCROLL, /* action */
+                1, /* pointerCount */
+                new MotionEvent.PointerProperties[] { pointerProperties },
+                new MotionEvent.PointerCoords[] { pointerCoords },
+                0, /* metaState */
+                0, /* buttonState */
+                0f, /* xPrecision */
+                0f, /* yPrecision */
+                0, /* deviceId */
+                0, /* edgeFlags */
+                source, /* source */
+                0 /* flags */
+        );
+    }
+
     private void executeWhileDragging(
             int dragY,
             Runnable duringDrag,
@@ -1711,4 +1810,18 @@
             return view;
         }
     }
+
+    private static class CaptureOnReleaseEdgeEffect extends EdgeEffect {
+        public boolean onReleaseCalled;
+
+        CaptureOnReleaseEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onRelease() {
+            onReleaseCalled = true;
+            super.onRelease();
+        }
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
index 8bebb57..8459ae0 100644
--- a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
@@ -36,6 +36,8 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Xml;
+import android.view.InputDevice;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
@@ -984,6 +986,104 @@
         assertTrue(edgeEffect.onAbsorbVelocity > 0);
     }
 
+    @Test
+    public void scrollFromRotaryStretchesTop() throws Throwable {
+        showOnlyStretch();
+
+        CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowTop = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(2f, InputDevice.SOURCE_ROTARY_ENCODER)));
+            assertFalse(edgeEffect.isFinished());
+            assertTrue(edgeEffect.getDistance() > 0f);
+            assertTrue(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromMouseDoesNotStretchTop() throws Throwable {
+        showOnlyStretch();
+
+        CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowTop = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(2f, InputDevice.SOURCE_MOUSE)));
+            assertTrue(edgeEffect.isFinished());
+            assertFalse(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromRotaryStretchesBottom() throws Throwable {
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the bottom
+            mScrollViewStretch.scrollTo(0, 210);
+        });
+
+        CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(-2f, InputDevice.SOURCE_ROTARY_ENCODER)));
+            assertFalse(edgeEffect.isFinished());
+            assertTrue(edgeEffect.getDistance() > 0f);
+            assertTrue(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    @Test
+    public void scrollFromMouseDoesNotStretchBottom() throws Throwable {
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the bottom
+            mScrollViewStretch.scrollTo(0, 210);
+        });
+
+        CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mScrollViewStretch.dispatchGenericMotionEvent(
+                    createScrollEvent(-2f, InputDevice.SOURCE_MOUSE)));
+            assertTrue(edgeEffect.isFinished());
+            assertFalse(edgeEffect.onReleaseCalled);
+        });
+    }
+
+    private MotionEvent createScrollEvent(float scrollAmount, int source) {
+        MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+        pointerProperties.toolType = MotionEvent.TOOL_TYPE_MOUSE;
+        MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+        int axis = source == InputDevice.SOURCE_ROTARY_ENCODER ? MotionEvent.AXIS_SCROLL
+                : MotionEvent.AXIS_VSCROLL;
+        pointerCoords.setAxisValue(axis, scrollAmount);
+
+        return MotionEvent.obtain(
+                0, /* downTime */
+                0, /* eventTime */
+                MotionEvent.ACTION_SCROLL, /* action */
+                1, /* pointerCount */
+                new MotionEvent.PointerProperties[] { pointerProperties },
+                new MotionEvent.PointerCoords[] { pointerCoords },
+                0, /* metaState */
+                0, /* buttonState */
+                0f, /* xPrecision */
+                0f, /* yPrecision */
+                0, /* deviceId */
+                0, /* edgeFlags */
+                source, /* source */
+                0 /* flags */
+        );
+    }
+
     private void showOnlyStretch() throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             mScrollViewCustom.setVisibility(View.GONE);
@@ -1147,4 +1247,18 @@
             super.onAbsorb(velocity);
         }
     }
+
+    public static class CaptureOnReleaseEdgeEffect extends EdgeEffect {
+        public boolean onReleaseCalled;
+
+        public CaptureOnReleaseEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onRelease() {
+            onReleaseCalled = true;
+            super.onRelease();
+        }
+    }
 }