From 0863d83505d70417831a788370e7c70ad73b323b Mon Sep 17 00:00:00 2001 From: Titouan Thibaud Date: Thu, 28 Dec 2023 11:36:59 +0100 Subject: [PATCH] Bug 1854113 - Fix pull-to-refresh triggered after pinch-to-zoom and swipe gesture Co-authored-by: Hiroyuki Ikezoe Co-authored-by: Jonathan Almeida --- .../ui/widgets/VerticalSwipeRefreshLayout.kt | 10 +++++++- .../mozilla/components/ui/widgets/TestUtils.kt | 2 ++ .../ui/widgets/VerticalSwipeRefreshLayoutTest.kt | 30 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt index f25f6373be85..ed3acece577d 100644 --- a/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt +++ b/mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayout.kt @@ -40,6 +40,9 @@ class VerticalSwipeRefreshLayout @JvmOverloads constructor( private val doubleTapSlop = ViewConfiguration.get(context).scaledDoubleTapSlop private val doubleTapSlopSquare = doubleTapSlop * doubleTapSlop + @VisibleForTesting + internal var hadMultiTouch: Boolean = false + @Suppress("ComplexMethod", "ReturnCount") override fun onInterceptTouchEvent(event: MotionEvent): Boolean { // Setting "isEnabled = false" is recommended for users of this ViewGroup @@ -49,10 +52,15 @@ class VerticalSwipeRefreshLayout @JvmOverloads constructor( return false } + if (MotionEvent.ACTION_DOWN == event.action) { + hadMultiTouch = false + } + // Layman's scale gesture (with two fingers) detector. // Allows for quick, serial inference as opposed to using ScaleGestureDetector // which uses callbacks and would be hard to synchronize in the little time we have. - if (event.pointerCount > 1) { + if (event.pointerCount > 1 || hadMultiTouch) { + hadMultiTouch = true return false } diff --git a/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/TestUtils.kt b/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/TestUtils.kt index e965e785c05c..cda854782e22 100644 --- a/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/TestUtils.kt +++ b/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/TestUtils.kt @@ -21,6 +21,8 @@ object TestUtils { pointerCount++ } else if (action == MotionEvent.ACTION_DOWN) { pointerCount = 1 + } else if (previousEvent?.action == MotionEvent.ACTION_POINTER_UP) { + pointerCount-- } val properties = Array( diff --git a/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayoutTest.kt b/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayoutTest.kt index 433053d06247..b06e67be3f51 100644 --- a/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayoutTest.kt +++ b/mobile/android/android-components/components/ui/widgets/src/test/java/mozilla/components/ui/widgets/VerticalSwipeRefreshLayoutTest.kt @@ -9,6 +9,7 @@ import android.view.MotionEvent.ACTION_CANCEL import android.view.MotionEvent.ACTION_DOWN import android.view.MotionEvent.ACTION_MOVE import android.view.MotionEvent.ACTION_POINTER_DOWN +import android.view.MotionEvent.ACTION_POINTER_UP import android.view.MotionEvent.ACTION_UP import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -45,6 +46,35 @@ class VerticalSwipeRefreshLayoutTest { } @Test + fun `onInterceptTouchEvent should abort pull to refresh and return false if the motion was multitouch`() { + swipeLayout.isEnabled = true + swipeLayout.setOnChildScrollUpCallback { _, _ -> false } + + val downEvent = TestUtils.getMotionEvent(ACTION_DOWN) + val moveEvent = TestUtils.getMotionEvent(ACTION_MOVE, x = 0f, y = 5f, previousEvent = downEvent) + val secondFingerEvent = TestUtils.getMotionEvent(ACTION_POINTER_DOWN, previousEvent = moveEvent) + val secondFingerNextEvent = + TestUtils.getMotionEvent(ACTION_MOVE, x = 0f, y = 5f, previousEvent = secondFingerEvent) + val secondFingerUp = TestUtils.getMotionEvent(ACTION_POINTER_UP, previousEvent = secondFingerNextEvent) + val upEvent = TestUtils.getMotionEvent(ACTION_UP, previousEvent = secondFingerUp) + val newDownEvent = TestUtils.getMotionEvent(ACTION_DOWN) + + swipeLayout.onInterceptTouchEvent(downEvent) + assertFalse(swipeLayout.onInterceptTouchEvent(moveEvent)) + assertFalse(swipeLayout.hadMultiTouch) + assertFalse(swipeLayout.onInterceptTouchEvent(secondFingerEvent)) + assertTrue(swipeLayout.hadMultiTouch) + assertFalse(swipeLayout.onInterceptTouchEvent(secondFingerNextEvent)) + assertTrue(swipeLayout.hadMultiTouch) + assertFalse(swipeLayout.onInterceptTouchEvent(secondFingerUp)) + assertTrue(swipeLayout.hadMultiTouch) + assertFalse(swipeLayout.onInterceptTouchEvent(upEvent)) + assertTrue(swipeLayout.hadMultiTouch) + assertFalse(swipeLayout.onInterceptTouchEvent(newDownEvent)) + assertFalse(swipeLayout.hadMultiTouch) + } + + @Test fun `onInterceptTouchEvent should abort pull to refresh and return false if zoom is in progress`() { swipeLayout = spy(swipeLayout) val downEvent = TestUtils.getMotionEvent(ACTION_DOWN, 0f, 0f) -- 2.11.4.GIT