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