Test: fling while overscrolled ScrollView/HorizontalScrollView

Bug: 186430321

ScrollView and HorizontalScrollView was not allowing a fling
effect while overscrolling. This adds tests to ensure that
ScrollView and HorizontalScrollView can fling
while overscrolling.

Test: new test
Test: manual ApiDemos
Change-Id: Idaf208460a12792ad97aec5a4a0e20fb696e09db
diff --git a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
index 2b35073..8b7f6cf 100644
--- a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
@@ -17,6 +17,7 @@
 package android.widget.cts;
 
 import static android.widget.cts.util.StretchEdgeUtil.dragHoldAndRun;
+import static android.widget.cts.util.StretchEdgeUtil.fling;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -898,6 +899,37 @@
         );
     }
 
+    @Test
+    public void testFlingWhileStretchedLeft() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        ScrollViewTest.CaptureOnAbsorbEdgeEffect
+                edgeEffect = new ScrollViewTest.CaptureOnAbsorbEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowLeft = edgeEffect;
+        fling(mActivityRule, mScrollViewStretch, 300, 0);
+        assertTrue("Expecting greater than 0, but was " + edgeEffect.onAbsorbVelocity,
+                edgeEffect.onAbsorbVelocity > 0);
+    }
+
+    @Test
+    public void testFlingWhileStretchedRight() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the bottom
+            mScrollViewStretch.scrollTo(210, 0);
+        });
+
+        ScrollViewTest.CaptureOnAbsorbEdgeEffect
+                edgeEffect = new ScrollViewTest.CaptureOnAbsorbEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowRight = edgeEffect;
+        fling(mActivityRule, mScrollViewStretch, -300, 0);
+        assertTrue("Expecting greater than 0, but was " + edgeEffect.onAbsorbVelocity,
+                edgeEffect.onAbsorbVelocity > 0);
+    }
+
     private void showOnlyStretch() throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             mScrollViewCustom.setVisibility(View.GONE);
@@ -908,6 +940,8 @@
             ArrayList exclusionRects = new ArrayList();
             exclusionRects.add(exclusionRect);
             mScrollViewStretch.setSystemGestureExclusionRects(exclusionRects);
+            mActivity.findViewById(R.id.wrapped_stretch)
+                    .setSystemGestureExclusionRects(exclusionRects);
         });
     }
 
diff --git a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
index d94b491..9b29d13 100644
--- a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
@@ -17,6 +17,7 @@
 package android.widget.cts;
 
 import static android.widget.cts.util.StretchEdgeUtil.dragHoldAndRun;
+import static android.widget.cts.util.StretchEdgeUtil.fling;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -941,6 +942,33 @@
         assertTrue(StretchEdgeUtil.dragUpTapAndHoldStretches(mActivityRule, mScrollViewStretch));
     }
 
+    @Test
+    public void testFlingWhileStretchedTop() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        CaptureOnAbsorbEdgeEffect edgeEffect = new CaptureOnAbsorbEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowTop = edgeEffect;
+        fling(mActivityRule, mScrollViewStretch, 0, 300);
+        assertTrue(edgeEffect.onAbsorbVelocity > 0);
+    }
+
+    @Test
+    public void testFlingWhileStretchedBottom() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the bottom
+            mScrollViewStretch.scrollTo(0, 210);
+        });
+
+        CaptureOnAbsorbEdgeEffect edgeEffect = new CaptureOnAbsorbEdgeEffect(mActivity);
+        mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
+        fling(mActivityRule, mScrollViewStretch, 0, -300);
+        assertTrue(edgeEffect.onAbsorbVelocity > 0);
+    }
+
     private void showOnlyStretch() throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             mScrollViewCustom.setVisibility(View.GONE);
@@ -1090,4 +1118,18 @@
             super.requestDisallowInterceptTouchEvent(disallowIntercept);
         }
     }
+
+    public static class CaptureOnAbsorbEdgeEffect extends EdgeEffect {
+        public int onAbsorbVelocity;
+
+        public CaptureOnAbsorbEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onAbsorb(int velocity) {
+            onAbsorbVelocity = velocity;
+            super.onAbsorb(velocity);
+        }
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
index ff744b0..7fa0682 100644
--- a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
+++ b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
@@ -84,7 +84,7 @@
  * Drags an area of the screen and executes [onFinalMove] after sending the final drag
  * motion and [onUp] after the drag up event has been sent.
  */
-public fun dragAndExecute(
+fun dragAndExecute(
     activityRule: ActivityTestRule<*>,
     screenX: Int,
     screenY: Int,
@@ -118,6 +118,35 @@
 }
 
 /**
+ * Flings [view] from the center by ([deltaX], [deltaY]) pixels over 16 milliseconds.
+ */
+fun fling(
+    activityRule: ActivityTestRule<*>,
+    view: View,
+    deltaX: Int,
+    deltaY: Int
+) {
+    val locationOnScreen = IntArray(2)
+    activityRule.runOnUiThread {
+        view.getLocationOnScreen(locationOnScreen)
+    }
+
+    val screenX = locationOnScreen[0]
+    val screenY = locationOnScreen[1]
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    CtsTouchUtils.emulateDragGesture(instrumentation, activityRule,
+            screenX + (view.width / 2),
+            screenY + (view.height / 2),
+            deltaX,
+            deltaY,
+            16,
+            4,
+            null
+    )
+}
+
+/**
  * Drags inside [view] starting at coordinates ([viewX], [viewY]) relative to [view] and moving
  * ([deltaX], [deltaY]) pixels before lifting. A Bitmap is captured after the final drag event,
  * before the up event.