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

Commit cad79bfe authored by omarmt's avatar omarmt
Browse files

MultiPointerDraggable should not lose pointerInput events

In aosp/2156636 a lint was added: launching multiple
awaitPointerEventScope blocks might cause some of the input events to be
dropped leading to unexpected behaviors.

Now, there are two suspending pointer input modifiers. The first keeps
track of the number of fingers and the initial position, while the
second takes care the multidrag behavior.

Test: Manually tested.
Bug: 336710600
Flag: com.android.systemui.scene_container
Change-Id: I6403cd2f2ab750e502e4c2bff90116cef955b4ed
parent 008fa711
Loading
Loading
Loading
Loading
+80 −77
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ import androidx.compose.ui.util.fastSumBy
import com.android.compose.ui.util.SpaceVectorConverter
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.sign
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

@@ -143,8 +143,8 @@ internal class MultiPointerDraggableNode(
    CompositionLocalConsumerModifierNode,
    ObserverModifierNode,
    SpaceVectorConverter {
    private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
    private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
    private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
    private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
    private val velocityTracker = VelocityTracker()
    private var previousEnabled: Boolean = false

@@ -153,7 +153,7 @@ internal class MultiPointerDraggableNode(
            // Reset the pointer input whenever enabled changed.
            if (value != field) {
                field = value
                delegate.resetPointerInputHandler()
                pointerInput.resetPointerInputHandler()
            }
        }

@@ -173,7 +173,7 @@ internal class MultiPointerDraggableNode(
            if (value != field) {
                field = value
                converter = SpaceVectorConverter(value)
                delegate.resetPointerInputHandler()
                pointerInput.resetPointerInputHandler()
            }
        }

@@ -186,19 +186,26 @@ internal class MultiPointerDraggableNode(
        observeReads {
            val newEnabled = enabled()
            if (newEnabled != previousEnabled) {
                delegate.resetPointerInputHandler()
                pointerInput.resetPointerInputHandler()
            }
            previousEnabled = newEnabled
        }
    }

    override fun onCancelPointerInput() = delegate.onCancelPointerInput()
    override fun onCancelPointerInput() {
        pointerTracker.onCancelPointerInput()
        pointerInput.onCancelPointerInput()
    }

    override fun onPointerEvent(
        pointerEvent: PointerEvent,
        pass: PointerEventPass,
        bounds: IntSize
    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
    ) {
        // The order is important here: the tracker is always called first.
        pointerTracker.onPointerEvent(pointerEvent, pass, bounds)
        pointerInput.onPointerEvent(pointerEvent, pass, bounds)
    }

    private var startedPosition: Offset? = null
    private var pointersDown: Int = 0
@@ -211,38 +218,36 @@ internal class MultiPointerDraggableNode(
        )
    }

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

        coroutineScope {
            launch {
    private suspend fun PointerInputScope.pointerTracker() {
        val currentContext = currentCoroutineContext()
        awaitPointerEventScope {
            // Intercepts pointer inputs and exposes [PointersInfo], via
            // [requireAncestorPointersInfoOwner], to our descendants.
                awaitPointerEventScope {
                    while (isActive) {
            while (currentContext.isActive) {
                // During the Initial pass, we receive the event after our ancestors.
                val pointers = awaitPointerEvent(PointerEventPass.Initial).changes

                pointersDown = pointers.countDown()
                if (pointersDown == 0) {
                    // There are no more pointers down
                    startedPosition = null
                } else if (startedPosition == null) {
                    startedPosition = pointers.first().position
                    if (enabled()) {
                        onFirstPointerDown()
                    }
                }
            }
        }
    }

            // The order is important here: we want to make sure that the previous PointerEventScope
            // is initialized first. This ensures that the following PointerEventScope doesn't
            // receive more events than the first one.
            launch {
    private suspend fun PointerInputScope.pointerInput() {
        if (!enabled()) {
            return
        }

        val currentContext = currentCoroutineContext()
        awaitPointerEventScope {
                    while (isActive) {
            while (currentContext.isActive) {
                try {
                    detectDragGestures(
                        orientation = orientation,
@@ -282,15 +287,13 @@ internal class MultiPointerDraggableNode(
                    )
                } catch (exception: CancellationException) {
                    // If the coroutine scope is active, we can just restart the drag cycle.
                            if (!isActive) {
                    if (!currentContext.isActive) {
                        throw exception
                    }
                }
            }
        }
    }
        }
    }

    /**
     * Start a fling gesture in another CoroutineScope, this is to ensure that even when the pointer