Account for delays when sending drags in FakeDragTest

In FakeDragTest, calls to ViewPager2's fake drag methods are scheduled
such that it results in a smooth drag. But the actual time at which
those callbacks are executed is unreliable (too late), which influences
the velocity calculated by Viewpager2. Sometimes this results in a
velocity above the fling threshold when a velocity below the threshold
was expected, which causes a fling to happen when it shouldn't.

Fix this by posting callback i after executing callback i-1, so there
will be a guaranteed delay between the two. Also, actively suppress
flings when we don't want them by extending the fake drag with 100ms of
zero motion. That guarantees the VelocityTracker returns a velocity of
0.

Bug: 129296837
Test: ./gradlew viewpager2:connectedCheck
Change-Id: Ib0a8f057a80ea20280029899545691e4c78e2669
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
index 72d1a12..bdf3d2c 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -86,17 +86,17 @@
 
     @Test
     fun test_flingToNextPage() {
-        basicFakeDragTest(.2f, 100, 1)
+        basicFakeDragTest(.2f, 100, 1, suppressFling = false)
     }
 
     @Test
     fun test_peekNextPage() {
-        basicFakeDragTest(.1f, 200, 0, DecelerateInterpolator())
+        basicFakeDragTest(.1f, 200, 0, DecelerateInterpolator(), true)
     }
 
     @Test
     fun test_flingCompletelyToNextPage() {
-        basicFakeDragTest(1f, 100, 1)
+        basicFakeDragTest(1f, 100, 1, suppressFling = false)
     }
 
     @Test
@@ -109,11 +109,11 @@
         //   |/
         // 0 +--------------
         //   0             1
-        basicFakeDragTest(.2f, 500, 0, PathInterpolatorCompat.create(Path().also {
+        basicFakeDragTest(.2f, 300, 0, PathInterpolatorCompat.create(Path().also {
             it.moveTo(0f, 0f)
             it.cubicTo(.4f, 6f, .5f, 1f, .8f, 1f)
             it.lineTo(1f, 1f)
-        }))
+        }), true)
     }
 
     @Test
@@ -126,10 +126,10 @@
         //   |/
         // 0 +-------
         //   0      1
-        basicFakeDragTest(.7f, 400, 0, PathInterpolatorCompat.create(Path().also {
+        basicFakeDragTest(.7f, 200, 0, PathInterpolatorCompat.create(Path().also {
             it.moveTo(0f, 0f)
             it.cubicTo(.4f, 1.3f, .7f, 1.5f, 1f, 1f)
-        }))
+        }), false)
     }
 
     @Test
@@ -243,7 +243,8 @@
         relativeDragDistance: Float,
         duration: Long,
         expectedFinalPage: Int,
-        interpolator: Interpolator = LinearInterpolator()
+        interpolator: Interpolator = LinearInterpolator(),
+        suppressFling: Boolean = false
     ) {
         val startPage = test.viewPager.currentItem
         // Run the test two times to verify that state doesn't linger
@@ -253,7 +254,7 @@
             val recorder = test.viewPager.addNewRecordingCallback()
 
             val latch = test.viewPager.addWaitForIdleLatch()
-            fakeDragger.fakeDrag(relativeDragDistance, duration, interpolator)
+            fakeDragger.fakeDrag(relativeDragDistance, duration, interpolator, suppressFling)
             latch.await(2000 + duration, MILLISECONDS)
 
             // test assertions
@@ -301,7 +302,7 @@
 
         // start fake drag
         val idleLatch = test.viewPager.addWaitForIdleLatch()
-        fakeDragger.fakeDrag(dragDistance(), 200)
+        fakeDragger.fakeDrag(dragDistance(), 100)
         assertThat(idleLatch.await(2, SECONDS), equalTo(true))
 
         // test assertions
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiperFakeDrag.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiperFakeDrag.kt
index 63de11b..66a3b80 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiperFakeDrag.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiperFakeDrag.kt
@@ -32,6 +32,8 @@
         // 60 fps
         private const val FRAME_LENGTH_MS = 1000L / 60
         private const val FLING_DURATION_MS = 100L
+        // VelocityTracker only considers evens in the last 100 ms
+        private const val COOL_DOWN_TIME_MS = 100L
     }
 
     private val needsRtlModifier
@@ -39,17 +41,18 @@
                 ViewCompat.getLayoutDirection(viewPager) == ViewCompat.LAYOUT_DIRECTION_RTL
 
     override fun swipeNext() {
-        fakeDrag(.5f, interpolator = AccelerateInterpolator())
+        fakeDrag(.5f, FLING_DURATION_MS, interpolator = AccelerateInterpolator())
     }
 
     override fun swipePrevious() {
-        fakeDrag(-.5f, interpolator = AccelerateInterpolator())
+        fakeDrag(-.5f, FLING_DURATION_MS, interpolator = AccelerateInterpolator())
     }
 
     fun fakeDrag(
         relativeDragDistance: Float,
-        duration: Long = FLING_DURATION_MS,
-        interpolator: Interpolator = LinearInterpolator()
+        duration: Long,
+        interpolator: Interpolator = LinearInterpolator(),
+        suppressFling: Boolean = false
     ) {
         // Generate the deltas to feed to fakeDragBy()
         val rtlModifier = if (needsRtlModifier) -1 else 1
@@ -62,14 +65,54 @@
         }
 
         // Send the fakeDrag events
-        var eventTime = SystemClock.uptimeMillis()
-        val delayMs = { eventTime - SystemClock.uptimeMillis() }
-        viewPager.post { viewPager.beginFakeDrag() }
-        for (delta in deltas) {
-            eventTime += FRAME_LENGTH_MS
-            viewPager.postDelayed({ viewPager.fakeDragBy(delta) }, delayMs())
+        viewPager.post {
+            viewPager.beginFakeDrag()
+            viewPager.postDelayed(FakeDragExecutor(deltas, suppressFling), FRAME_LENGTH_MS)
         }
-        eventTime++
-        viewPager.postDelayed({ viewPager.endFakeDrag() }, delayMs())
+    }
+
+    private inner class FakeDragExecutor(
+        private val deltas: List<Float>,
+        private val suppressFling: Boolean
+    ) : Runnable {
+        private var nextStep = 0
+        private val stepsLeft get() = nextStep < deltas.size
+        // If suppressFling, end with cool down period to make sure VelocityTracker has 0 velocity
+        private var coolingDown = false
+        private var coolDownStart = 0L
+
+        override fun run() {
+            if (coolingDown) {
+                doCoolDownStep()
+            } else {
+                doFakeDragStep()
+            }
+        }
+
+        private fun doFakeDragStep() {
+            viewPager.fakeDragBy(deltas[nextStep])
+            nextStep++
+
+            when {
+                stepsLeft -> viewPager.postDelayed(this, FRAME_LENGTH_MS)
+                suppressFling -> startCoolDown()
+                else -> viewPager.endFakeDrag()
+            }
+        }
+
+        private fun startCoolDown() {
+            coolingDown = true
+            coolDownStart = SystemClock.uptimeMillis()
+            viewPager.postDelayed(this, FRAME_LENGTH_MS)
+        }
+
+        private fun doCoolDownStep() {
+            viewPager.fakeDragBy(0f)
+            if (SystemClock.uptimeMillis() <= coolDownStart + COOL_DOWN_TIME_MS) {
+                viewPager.postDelayed(this, FRAME_LENGTH_MS)
+            } else {
+                viewPager.endFakeDrag()
+            }
+        }
     }
 }
\ No newline at end of file