Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b58741b authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Don't install a pointer input when there are no user actions

This CL checks whether our swipeToScene() modifiers should be enabled
during composition (instead of during gesture, prior to this CL) so that
gestures will pass through a SceneTransitionLayout when there is no user
actions defined on the current scene.

Bug: 370913106
Test: atest SwipeToSceneTest
Flag: com.android.systemui.scene_container
Change-Id: I77e2fa7a57ad367ab7a405ba53ee5511f98ba848
parent efc0c5a3
Loading
Loading
Loading
Loading
+1 −41
Original line number Diff line number Diff line
@@ -42,10 +42,8 @@ import androidx.compose.ui.input.pointer.util.addPointerInputChange
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
@@ -79,7 +77,6 @@ import kotlinx.coroutines.launch
@Stable
internal fun Modifier.multiPointerDraggable(
    orientation: Orientation,
    enabled: () -> Boolean,
    startDragImmediately: (startedPosition: Offset) -> Boolean,
    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
    onFirstPointerDown: () -> Unit = {},
@@ -89,7 +86,6 @@ internal fun Modifier.multiPointerDraggable(
    this.then(
        MultiPointerDraggableElement(
            orientation,
            enabled,
            startDragImmediately,
            onDragStarted,
            onFirstPointerDown,
@@ -100,7 +96,6 @@ internal fun Modifier.multiPointerDraggable(

private data class MultiPointerDraggableElement(
    private val orientation: Orientation,
    private val enabled: () -> Boolean,
    private val startDragImmediately: (startedPosition: Offset) -> Boolean,
    private val onDragStarted:
        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
@@ -111,7 +106,6 @@ private data class MultiPointerDraggableElement(
    override fun create(): MultiPointerDraggableNode =
        MultiPointerDraggableNode(
            orientation = orientation,
            enabled = enabled,
            startDragImmediately = startDragImmediately,
            onDragStarted = onDragStarted,
            onFirstPointerDown = onFirstPointerDown,
@@ -121,7 +115,6 @@ private data class MultiPointerDraggableElement(

    override fun update(node: MultiPointerDraggableNode) {
        node.orientation = orientation
        node.enabled = enabled
        node.startDragImmediately = startDragImmediately
        node.onDragStarted = onDragStarted
        node.onFirstPointerDown = onFirstPointerDown
@@ -131,7 +124,6 @@ private data class MultiPointerDraggableElement(

internal class MultiPointerDraggableNode(
    orientation: Orientation,
    enabled: () -> Boolean,
    var startDragImmediately: (startedPosition: Offset) -> Boolean,
    var onDragStarted:
        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
@@ -142,21 +134,10 @@ internal class MultiPointerDraggableNode(
    DelegatingNode(),
    PointerInputModifierNode,
    CompositionLocalConsumerModifierNode,
    ObserverModifierNode,
    SpaceVectorConverter {
    private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
    private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
    private val velocityTracker = VelocityTracker()
    private var previousEnabled: Boolean = false

    var enabled: () -> Boolean = enabled
        set(value) {
            // Reset the pointer input whenever enabled changed.
            if (value != field) {
                field = value
                pointerInput.resetPointerInputHandler()
            }
        }

    private var converter = SpaceVectorConverter(orientation)

@@ -178,21 +159,6 @@ internal class MultiPointerDraggableNode(
            }
        }

    override fun onAttach() {
        previousEnabled = enabled()
        onObservedReadsChanged()
    }

    override fun onObservedReadsChanged() {
        observeReads {
            val newEnabled = enabled()
            if (newEnabled != previousEnabled) {
                pointerInput.resetPointerInputHandler()
            }
            previousEnabled = newEnabled
        }
    }

    override fun onCancelPointerInput() {
        pointerTracker.onCancelPointerInput()
        pointerInput.onCancelPointerInput()
@@ -253,10 +219,8 @@ internal class MultiPointerDraggableNode(
                        velocityTracker.resetTracking()
                        velocityTracker.addPointerInputChange(firstPointerDown)
                        startedPosition = firstPointerDown.position
                        if (enabled()) {
                        onFirstPointerDown()
                    }
                    }

                    // Changes with at least one pointer
                    else -> {
@@ -294,10 +258,6 @@ internal class MultiPointerDraggableNode(
    }

    private suspend fun PointerInputScope.pointerInput() {
        if (!enabled()) {
            return
        }

        val currentContext = currentCoroutineContext()
        awaitPointerEventScope {
            while (currentContext.isActive) {
+23 −19
Original line number Diff line number Diff line
@@ -41,7 +41,28 @@ internal fun Modifier.swipeToScene(
    draggableHandler: DraggableHandlerImpl,
    swipeDetector: SwipeDetector,
): Modifier {
    return this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
    return if (draggableHandler.enabled()) {
        this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
    } else {
        this
    }
}

private fun DraggableHandlerImpl.enabled(): Boolean {
    return isDrivingTransition || contentForSwipes().shouldEnableSwipes(orientation)
}

private fun DraggableHandlerImpl.contentForSwipes(): Content {
    return layoutImpl.contentForUserActions()
}

/** Whether swipe should be enabled in the given [orientation]. */
private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
    if (userActions.isEmpty()) {
        return false
    }

    return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
}

private data class SwipeToSceneElement(
@@ -64,7 +85,6 @@ private class SwipeToSceneNode(
        delegate(
            MultiPointerDraggableNode(
                orientation = draggableHandler.orientation,
                enabled = ::enabled,
                startDragImmediately = ::startDragImmediately,
                onDragStarted = draggableHandler::onDragStarted,
                onFirstPointerDown = ::onFirstPointerDown,
@@ -124,22 +144,6 @@ private class SwipeToSceneNode(

    override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()

    private fun enabled(): Boolean {
        return draggableHandler.isDrivingTransition ||
            contentForSwipes().shouldEnableSwipes(multiPointerDraggableNode.orientation)
    }

    private fun contentForSwipes(): Content {
        return draggableHandler.layoutImpl.contentForUserActions()
    }

    /** Whether swipe should be enabled in the given [orientation]. */
    private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
        return userActions.keys.any {
            it is Swipe.Resolved && it.direction.orientation == orientation
        }
    }

    private fun startDragImmediately(startedPosition: Offset): Boolean {
        // Immediately start the drag if the user can't swipe in the other direction and the gesture
        // handler can intercept it.
@@ -152,7 +156,7 @@ private class SwipeToSceneNode(
                Orientation.Vertical -> Orientation.Horizontal
                Orientation.Horizontal -> Orientation.Vertical
            }
        return contentForSwipes().shouldEnableSwipes(oppositeOrientation)
        return draggableHandler.contentForSwipes().shouldEnableSwipes(oppositeOrientation)
    }
}

+15 −21
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.modifiers.thenIf
import com.android.compose.nestedscroll.SuspendedValue
import com.google.common.truth.Truth.assertThat
import kotlin.properties.Delegates
@@ -94,9 +95,9 @@ class MultiPointerDraggableTest {
            Box(
                Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                    .thenIf(enabled) {
                        Modifier.multiPointerDraggable(
                            orientation = Orientation.Vertical,
                        enabled = { enabled },
                            startDragImmediately = { false },
                            onDragStarted = { _, _, _ ->
                                started = true
@@ -107,6 +108,7 @@ class MultiPointerDraggableTest {
                            },
                            dispatcher = defaultDispatcher,
                        )
                    }
            )
        }

@@ -164,7 +166,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        enabled = { true },
                        // We want to start a drag gesture immediately
                        startDragImmediately = { true },
                        onDragStarted = { _, _, _ ->
@@ -238,7 +239,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        enabled = { true },
                        startDragImmediately = { false },
                        onDragStarted = { _, _, _ ->
                            started = true
@@ -358,7 +358,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        enabled = { true },
                        startDragImmediately = { false },
                        onDragStarted = { _, _, _ ->
                            started = true
@@ -464,7 +463,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        enabled = { true },
                        startDragImmediately = { false },
                        onDragStarted = { _, _, _ ->
                            verticalStarted = true
@@ -477,7 +475,6 @@ class MultiPointerDraggableTest {
                    )
                    .multiPointerDraggable(
                        orientation = Orientation.Horizontal,
                        enabled = { true },
                        startDragImmediately = { false },
                        onDragStarted = { _, _, _ ->
                            horizontalStarted = true
@@ -570,7 +567,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        enabled = { true },
                        startDragImmediately = { false },
                        swipeDetector =
                            object : SwipeDetector {
@@ -672,7 +668,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        enabled = { true },
                        startDragImmediately = { false },
                        onDragStarted = { _, _, _ ->
                            SimpleDragController(
@@ -744,7 +739,6 @@ class MultiPointerDraggableTest {
                    .nestedScrollDispatcher()
                    .multiPointerDraggable(
                        orientation = Orientation.Vertical,
                        enabled = { true },
                        startDragImmediately = { false },
                        onDragStarted = { _, _, _ ->
                            SimpleDragController(
+32 −0
Original line number Diff line number Diff line
@@ -22,11 +22,15 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -36,8 +40,11 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
@@ -844,4 +851,29 @@ class SwipeToSceneTest {
        assertThat(transition.progress).isEqualTo(1f)
        assertThat(availableOnPostScroll).isEqualTo(ovescrollPx)
    }

    @Test
    fun sceneWithoutSwipesDoesNotConsumeGestures() {
        val buttonTag = "button"

        rule.setContent {
            Box {
                var count by remember { mutableStateOf(0) }
                Button(onClick = { count++ }, Modifier.testTag(buttonTag).align(Alignment.Center)) {
                    Text("Count: $count")
                }

                SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
                    scene(SceneA) { Box(Modifier.fillMaxSize()) }
                }
            }
        }

        rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 0")

        // Click on the root at its center, where the button is located. Clicks should go through
        // the STL and reach the button given that there is no swipes for the current scene.
        repeat(3) { rule.onRoot().performClick() }
        rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 3")
    }
}