Merge "Support for multiple gestures at once" into main
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index d70a248..2dc53ab 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -157,6 +157,8 @@
      */
     private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() }
 
+    internal var gestureWithPriority: Any? = null
+
     internal fun onDragStarted() {
         if (isDrivingTransition) {
             // This [transition] was already driving the animation: simply take over it.
@@ -525,15 +527,21 @@
     private val gestureHandler: SceneGestureHandler,
 ) : DraggableHandler {
     override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) {
+        gestureHandler.gestureWithPriority = this
         gestureHandler.onDragStarted()
     }
 
     override fun onDelta(pixels: Float) {
-        gestureHandler.onDrag(delta = pixels)
+        if (gestureHandler.gestureWithPriority == this) {
+            gestureHandler.onDrag(delta = pixels)
+        }
     }
 
     override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) {
-        gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
+        if (gestureHandler.gestureWithPriority == this) {
+            gestureHandler.gestureWithPriority = null
+            gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
+        }
     }
 }
 
@@ -615,10 +623,15 @@
             },
             canContinueScroll = { priorityScene == gestureHandler.swipeTransitionToScene.key },
             onStart = {
+                gestureHandler.gestureWithPriority = this
                 priorityScene = nextScene
                 gestureHandler.onDragStarted()
             },
             onScroll = { offsetAvailable ->
+                if (gestureHandler.gestureWithPriority != this) {
+                    return@PriorityNestedScrollConnection Offset.Zero
+                }
+
                 val amount = offsetAvailable.toAmount()
 
                 // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
@@ -628,6 +641,10 @@
                 amount.toOffset()
             },
             onStop = { velocityAvailable ->
+                if (gestureHandler.gestureWithPriority != this) {
+                    return@PriorityNestedScrollConnection Velocity.Zero
+                }
+
                 priorityScene = null
 
                 gestureHandler.onDragStopped(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 9b9e70d..6791a85 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -312,4 +312,52 @@
         advanceUntilIdle()
         assertScene(currentScene = SceneA, isIdle = true)
     }
+
+    @Test
+    fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
+        draggable.onDelta(deltaInPixels10)
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+    @Test
+    fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
+        draggable.onDragStopped(coroutineScope, velocityThreshold)
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
+        nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+        assertScene(currentScene = SceneA, isIdle = true)
+    }
+
+    @Test
+    fun startNestedScrollWhileDragging() = runGestureTest {
+        draggable.onDragStarted(coroutineScope, Offset.Zero)
+        assertScene(currentScene = SceneA, isIdle = false)
+        val transition = transitionState as Transition
+
+        draggable.onDelta(deltaInPixels10)
+        assertThat(transition.progress).isEqualTo(0.1f)
+
+        // now we can intercept the scroll events
+        nestedScrollEvents(available = offsetY10)
+        assertThat(transition.progress).isEqualTo(0.2f)
+
+        // this should be ignored, we are scrolling now!
+        draggable.onDragStopped(coroutineScope, velocityThreshold)
+        assertScene(currentScene = SceneA, isIdle = false)
+
+        nestedScrollEvents(available = offsetY10)
+        assertThat(transition.progress).isEqualTo(0.3f)
+
+        nestedScrollEvents(available = offsetY10)
+        assertThat(transition.progress).isEqualTo(0.4f)
+
+        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+        assertScene(currentScene = SceneC, isIdle = false)
+
+        // wait for the stop animation
+        advanceUntilIdle()
+        assertScene(currentScene = SceneC, isIdle = true)
+    }
 }