Handling the removal of PointerInputFilters synchronously.
Subcomposition can happen during the dispatch of pointer
input events. When this occurred, it used to crash. Now it
doesn't.
Relnote: "Fixed issue where pointer input dispatch would
cause a crash if PointerInputFilters were removed via
subcomposition during disptach. This is now fixed."
Fixes: 157998762
Test: ./gradlew ui:ui-core:testDebugUnitTest --tests=androidx.ui.core.pointerinput.HitPathTrackerTest
Change-Id: Iab398032792a8dde761ce5440650c774b4d56022
diff --git a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt
index 02b4d9e1..abfdd6d 100644
--- a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt
+++ b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/CoreDemos.kt
@@ -34,6 +34,7 @@
import androidx.ui.core.demos.gestures.ScaleGestureFilterDemo
import androidx.ui.core.demos.gestures.DragGestureFilterDemo
import androidx.ui.core.demos.gestures.LongPressDragGestureFilterDemo
+import androidx.ui.core.demos.gestures.PointerInputDuringSubComp
import androidx.ui.core.demos.keyinput.KeyInputDemo
import androidx.ui.core.demos.viewinterop.ViewInComposeDemo
import androidx.ui.demos.common.ComposableDemo
@@ -61,7 +62,8 @@
ComposableDemo("Drag and Scale") { DragAndScaleGestureDetectorDemo() },
ComposableDemo("Popup Drag") { PopupDragDemo() },
ComposableDemo("Double Tap in Tap") { DoubleTapInTapDemo() },
- ComposableDemo("Nested Long Press") { NestedLongPressDemo() }
+ ComposableDemo("Nested Long Press") { NestedLongPressDemo() },
+ ComposableDemo("Pointer Input During Sub Comp") { PointerInputDuringSubComp() }
))
))
diff --git a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/SurviveThroughSubComp.kt b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/SurviveThroughSubComp.kt
new file mode 100644
index 0000000..0af4c8d
--- /dev/null
+++ b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/gestures/SurviveThroughSubComp.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.core.demos.gestures
+
+import androidx.compose.Composable
+import androidx.compose.remember
+import androidx.compose.state
+import androidx.ui.core.Alignment
+import androidx.ui.core.Modifier
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
+import androidx.ui.core.changedToDownIgnoreConsumed
+import androidx.ui.core.changedToUpIgnoreConsumed
+import androidx.ui.core.composed
+import androidx.ui.core.pointerinput.PointerInputFilter
+import androidx.ui.core.pointerinput.PointerInputModifier
+import androidx.ui.foundation.AdapterList
+import androidx.ui.foundation.Box
+import androidx.ui.foundation.Text
+import androidx.ui.foundation.drawBackground
+import androidx.ui.foundation.drawBorder
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
+import androidx.ui.layout.fillMaxSize
+import androidx.ui.layout.size
+import androidx.ui.layout.wrapContentSize
+import androidx.ui.unit.IntPxSize
+import androidx.ui.unit.TextUnit
+import androidx.ui.unit.dp
+
+/**
+ * Demonstration of how various press/tap gesture interact together in a nested fashion.
+ */
+@Composable
+fun PointerInputDuringSubComp() {
+ Column {
+ Text(
+ "Demonstrates that PointerInputFilters that are currently receiving pointer input " +
+ "events can be removed from the hierarchy by sub composition with no difficulty"
+ )
+ Text(
+ "Below is an AdapterList with many touchable items. Each item keeps track of the " +
+ "number of pointers touching it. If you touch an item and then scroll so " +
+ "that it goes out of the viewport and then back into the viewport, you will" +
+ " see that it no longer knows that a finger is touching it. That is because " +
+ "it is actually a new item that has not been hit tested yet. If you keep " +
+ "your finger there and then add more fingers, it will track those new fingers."
+ )
+ AdapterList(
+ List(100) { index -> index },
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .size(200.dp)
+ .drawBackground(Color.White)
+ ) {
+ val pointerCount = state { 0 }
+
+ Box(
+ Modifier.size(200.dp)
+ .drawBorder(size = 1.dp, color = Color.Black)
+ .pointerCounterGestureFilter { newCount -> pointerCount.value = newCount }
+ ) {
+ Text(
+ "${pointerCount.value}",
+ fontSize = TextUnit.Em(16),
+ color = Color.Black,
+ modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.Center)
+ )
+ }
+ }
+ }
+}
+
+fun Modifier.pointerCounterGestureFilter(
+ onPointerCountChanged: (Int) -> Unit
+): Modifier =
+ composed {
+ val filter = remember { PointerCounterGestureFilter() }
+ filter.>
+ PointerInputModifierImpl(filter)
+ }
+
+internal class PointerInputModifierImpl(override val pointerInputFilter: PointerInputFilter) :
+ PointerInputModifier
+
+internal class PointerCounterGestureFilter : PointerInputFilter() {
+
+ lateinit var onPointerCountChanged: (resultingPointerCount: Int) -> Unit
+
+ override fun onPointerInput(
+ changes: List<PointerInputChange>,
+ pass: PointerEventPass,
+ bounds: IntPxSize
+ ): List<PointerInputChange> {
+ if (pass == PointerEventPass.PostUp) {
+ if (changes.any {
+ it.changedToDownIgnoreConsumed() || it.changedToUpIgnoreConsumed()
+ }) {
+ onPointerCountChanged.invoke(changes.count { it.current.down })
+ }
+ }
+ return changes
+ }
+
+ override fun onCancel() {}
+}
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt b/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt
index 2999035..caf0ecf 100644
--- a/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/core/pointerinput/HitPathTracker.kt
@@ -404,6 +404,7 @@
downPass: PointerEventPass,
upPass: PointerEventPass?
): Boolean {
+
// Filter for changes that are associated with pointer ids that are relevant to this node.
val relevantChanges =
pointerInputChanges.filterTo(mutableMapOf()) { entry ->
@@ -415,28 +416,34 @@
return false
}
- // For each relevant change:
- // 1. subtract the offset
- // 2. dispatch the change on the down pass,
- // 3. update it in relevantChanges.
- relevantChanges.let {
- // TODO(shepshapard): would be nice if we didn't have to subtract and then add
- // offsets. This is currently done because the calculated offsets are currently
- // global, not relative to eachother.
- it.subtractOffset(pointerInputFilter.position)
- it.dispatchToPointerInputFilter(pointerInputFilter, downPass, pointerInputFilter.size)
- it.addOffset(pointerInputFilter.position)
+ // TODO(b/158243568): For this attached check, and all of the following checks like this, we
+ // should ideally be dispatching cancel to the sub tree with this node as it's root, and
+ // we should remove the same sub tree from the tracker. This will currently happen on
+ // the next dispatch of events, but we shouldn't have to wait for another event.
+ if (pointerInputFilter.isAttached) {
+ relevantChanges.let {
+ // TODO(shepshapard): would be nice if we didn't have to subtract and then add
+ // offsets. This is currently done because the calculated offsets are currently
+ // global, not relative to each other.
+ it.subtractOffset(pointerInputFilter.position)
+ it.dispatchToPointerInputFilter(
+ pointerInputFilter,
+ downPass,
+ pointerInputFilter.size
+ )
+ it.addOffset(pointerInputFilter.position)
+ }
}
- // Call children recursively with the relevant changes.
- children.forEach { it.dispatchChanges(relevantChanges, downPass, upPass) }
+ if (pointerInputFilter.isAttached) {
+ children.forEach { it.dispatchChanges(relevantChanges, downPass, upPass) }
+ }
- // For each relevant change:
- // 1. dispatch the change on the up pass,
- // 2. add the offset,
- // 3. update it in relevant changes.
- if (upPass != null) {
+ if (pointerInputFilter.isAttached && upPass != null) {
relevantChanges.let {
+ // TODO(shepshapard): would be nice if we didn't have to subtract and then add
+ // offsets. This is currently done because the calculated offsets are currently
+ // global, not relative to each other.
it.subtractOffset(pointerInputFilter.position)
it.dispatchToPointerInputFilter(pointerInputFilter, upPass, pointerInputFilter.size)
it.addOffset(pointerInputFilter.position)
@@ -445,7 +452,6 @@
// Mutate the pointerInputChanges with the ones we modified.
pointerInputChanges.putAll(relevantChanges)
-
return true
}
@@ -473,16 +479,22 @@
return
}
- if (this != dispatchingNode) {
+ // TODO(b/158243568): For this attached check, and all of the following checks like this, we
+ // should ideally be dispatching cancel to the sub tree with this node as it's root, and
+ // we should remove the same sub tree from the tracker. This will currently happen on
+ // the next dispatch of events, but we shouldn't have to wait for another event.
+ if (pointerInputFilter.isAttached && this != dispatchingNode) {
pointerInputFilter.onCustomEvent(event, downPass)
}
- // Call children recursively with the relevant changes.
- children.forEach {
- it.dispatchCustomEvent(event, relevantPointers, downPass, upPass, dispatchingNode)
+ if (pointerInputFilter.isAttached) {
+ // Call children recursively with the relevant changes.
+ children.forEach {
+ it.dispatchCustomEvent(event, relevantPointers, downPass, upPass, dispatchingNode)
+ }
}
- if (upPass != null && this != dispatchingNode) {
+ if (pointerInputFilter.isAttached && upPass != null && this != dispatchingNode) {
pointerInputFilter.onCustomEvent(event, upPass)
}
}
diff --git a/ui/ui-core/src/test/java/androidx/ui/core/pointerinput/HitPathTrackerTest.kt b/ui/ui-core/src/test/java/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
index e2b89f1..f2410db 100644
--- a/ui/ui-core/src/test/java/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
+++ b/ui/ui-core/src/test/java/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
@@ -964,15 +964,15 @@
// Arrange.
- val pif1 = PointerInputFilterMock(isAttached = true)
- val pif2 = PointerInputFilterMock(isAttached = true)
- val pif3 = PointerInputFilterMock(isAttached = true)
- val pif4 = PointerInputFilterMock(isAttached = true)
- val pif5 = PointerInputFilterMock(isAttached = true)
- val pif6 = PointerInputFilterMock(isAttached = true)
- val pif7 = PointerInputFilterMock(isAttached = true)
- val pif8 = PointerInputFilterMock(isAttached = true)
- val pif9 = PointerInputFilterMock(isAttached = true)
+ val pif1 = PointerInputFilterMock()
+ val pif2 = PointerInputFilterMock()
+ val pif3 = PointerInputFilterMock()
+ val pif4 = PointerInputFilterMock()
+ val pif5 = PointerInputFilterMock()
+ val pif6 = PointerInputFilterMock()
+ val pif7 = PointerInputFilterMock()
+ val pif8 = PointerInputFilterMock()
+ val pif9 = PointerInputFilterMock()
val pointerId1 = PointerId(1)
val pointerId2 = PointerId(2)
@@ -1037,9 +1037,9 @@
// compositionRoot, root -> middle -> leaf
@Test
fun removeDetachedPointerInputFilters_1PathRootDetached_allRemovedAndCorrectCancels() {
- val root = PointerInputFilterMock(isAttached = false)
- val middle = PointerInputFilterMock(isAttached = false)
- val leaf = PointerInputFilterMock(isAttached = false)
+ val root = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
hitPathTracker.addHitPath(PointerId(0), listOf(root, middle, leaf))
@@ -1056,9 +1056,9 @@
// compositionRoot -> root, middle -> child
@Test
fun removeDetachedPointerInputFilters_1PathMiddleDetached_removesAndCancelsCorrect() {
- val root = PointerInputFilterMock(isAttached = true)
- val middle = PointerInputFilterMock(isAttached = false)
- val child = PointerInputFilterMock(isAttached = false)
+ val root = PointerInputFilterMock()
+ val middle = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val child = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId = PointerId(0)
hitPathTracker.addHitPath(pointerId, listOf(root, middle, child))
@@ -1082,9 +1082,9 @@
// compositionRoot -> root -> middle, leaf
@Test
fun removeDetachedPointerInputFilters_1PathLeafDetached_removesAndCancelsCorrect() {
- val root = PointerInputFilterMock(isAttached = true)
- val middle = PointerInputFilterMock(isAttached = true)
- val leaf = PointerInputFilterMock(isAttached = false)
+ val root = PointerInputFilterMock()
+ val middle = PointerInputFilterMock()
+ val leaf = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId = PointerId(0)
hitPathTracker.addHitPath(pointerId, listOf(root, middle, leaf))
@@ -1112,17 +1112,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots1Detached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = true)
- val middle1 = PointerInputFilterMock(isAttached = true)
- val leaf1 = PointerInputFilterMock(isAttached = true)
+ val root1 = PointerInputFilterMock()
+ val middle1 = PointerInputFilterMock()
+ val leaf1 = PointerInputFilterMock()
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = true)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock()
- val root3 = PointerInputFilterMock(isAttached = false)
- val middle3 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val root3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1176,17 +1176,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots1MiddleDetached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = true)
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val root1 = PointerInputFilterMock()
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = true)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock()
- val root3 = PointerInputFilterMock(isAttached = true)
- val middle3 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = true)
+ val root3 = PointerInputFilterMock()
+ val middle3 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock()
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1242,17 +1242,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots1LeafDetached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = true)
- val middle1 = PointerInputFilterMock(isAttached = true)
- val leaf1 = PointerInputFilterMock(isAttached = true)
+ val root1 = PointerInputFilterMock()
+ val middle1 = PointerInputFilterMock()
+ val leaf1 = PointerInputFilterMock()
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root3 = PointerInputFilterMock(isAttached = true)
- val middle3 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = true)
+ val root3 = PointerInputFilterMock()
+ val middle3 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock()
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1309,17 +1309,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots2Detached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = false)
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val root1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = true)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock()
- val root3 = PointerInputFilterMock(isAttached = false)
- val middle3 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val root3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1366,17 +1366,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots2MiddlesDetached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = true)
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val root1 = PointerInputFilterMock()
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root3 = PointerInputFilterMock(isAttached = true)
- val middle3 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = true)
+ val root3 = PointerInputFilterMock()
+ val middle3 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock()
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1429,17 +1429,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots2LeafsDetached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = true)
- val middle1 = PointerInputFilterMock(isAttached = true)
- val leaf1 = PointerInputFilterMock(isAttached = true)
+ val root1 = PointerInputFilterMock()
+ val middle1 = PointerInputFilterMock()
+ val leaf1 = PointerInputFilterMock()
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root3 = PointerInputFilterMock(isAttached = true)
- val middle3 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val root3 = PointerInputFilterMock()
+ val middle3 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1492,17 +1492,17 @@
// compositionRoot, root3 -> middle3 -> leaf3
@Test
fun removeDetachedPointerInputFilters_3Roots3Detached_allRemovedAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = false)
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val root1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root2 = PointerInputFilterMock(isAttached = false)
- val middle2 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val root2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root3 = PointerInputFilterMock(isAttached = false)
- val middle3 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val root3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
hitPathTracker.addHitPath(PointerId(3), listOf(root1, middle1, leaf1))
hitPathTracker.addHitPath(PointerId(5), listOf(root2, middle2, leaf2))
@@ -1536,17 +1536,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots3MiddlesDetached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = true)
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val root1 = PointerInputFilterMock()
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root3 = PointerInputFilterMock(isAttached = true)
- val middle3 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val root3 = PointerInputFilterMock()
+ val middle3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1594,17 +1594,17 @@
@Test
fun removeDetachedPointerInputFilters_3Roots3LeafsDetached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = true)
- val middle1 = PointerInputFilterMock(isAttached = true)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val root1 = PointerInputFilterMock()
+ val middle1 = PointerInputFilterMock()
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root3 = PointerInputFilterMock(isAttached = true)
- val middle3 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val root3 = PointerInputFilterMock()
+ val middle3 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1655,17 +1655,17 @@
@Test
fun removeDetachedPointerInputFilters_3RootsStaggeredDetached_removesAndCancelsCorrect() {
- val root1 = PointerInputFilterMock(isAttached = false)
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val root1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root2 = PointerInputFilterMock(isAttached = true)
- val middle2 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val root2 = PointerInputFilterMock()
+ val middle2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val root3 = PointerInputFilterMock(isAttached = true)
- val middle3 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val root3 = PointerInputFilterMock()
+ val middle3 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1711,16 +1711,16 @@
// middle3 -> leaf3
@Test
fun removeDetachedPointerInputFilters_rootWith3MiddlesDetached_allRemovedAndCorrectCancels() {
- val root = PointerInputFilterMock(isAttached = false)
+ val root = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val middle2 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val middle2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val middle3 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val middle3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
hitPathTracker.addHitPath(PointerId(3), listOf(root, middle1, leaf1))
hitPathTracker.addHitPath(PointerId(5), listOf(root, middle2, leaf2))
@@ -1755,16 +1755,16 @@
@Test
fun removeDetachedPointerInputFilters_rootWith3Middles1Detached_removesAndCancelsCorrect() {
- val root = PointerInputFilterMock(isAttached = true)
+ val root = PointerInputFilterMock()
- val middle1 = PointerInputFilterMock(isAttached = true)
- val leaf1 = PointerInputFilterMock(isAttached = true)
+ val middle1 = PointerInputFilterMock()
+ val leaf1 = PointerInputFilterMock()
- val middle2 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = true)
+ val middle2 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock()
- val middle3 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val middle3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1815,16 +1815,16 @@
@Test
fun removeDetachedPointerInputFilters_rootWith3Middles2Detached_removesAndCancelsCorrect() {
- val root = PointerInputFilterMock(isAttached = true)
+ val root = PointerInputFilterMock()
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val middle2 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val middle2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val middle3 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = true)
+ val middle3 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock()
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1871,16 +1871,16 @@
@Test
fun removeDetachedPointerInputFilters_rootWith3MiddlesAllDetached_allMiddlesRemoved() {
- val root = PointerInputFilterMock(isAttached = true)
+ val root = PointerInputFilterMock()
- val middle1 = PointerInputFilterMock(isAttached = false)
- val leaf1 = PointerInputFilterMock(isAttached = false)
+ val middle1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val middle2 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
+ val middle2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
- val middle3 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val middle3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1923,13 +1923,13 @@
@Test
fun removeDetachedPointerInputFilters_middleWith3Leafs1Detached_correctLeafRemoved() {
- val root = PointerInputFilterMock(isAttached = true)
+ val root = PointerInputFilterMock()
- val middle = PointerInputFilterMock(isAttached = true)
+ val middle = PointerInputFilterMock()
- val leaf1 = PointerInputFilterMock(isAttached = true)
- val leaf2 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = true)
+ val leaf1 = PointerInputFilterMock()
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock()
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -1975,13 +1975,13 @@
@Test
fun removeDetachedPointerInputFilters_middleWith3Leafs2Detached_correctLeafsRemoved() {
- val root = PointerInputFilterMock(isAttached = true)
+ val root = PointerInputFilterMock()
- val middle = PointerInputFilterMock(isAttached = true)
+ val middle = PointerInputFilterMock()
- val leaf1 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = true)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock()
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -2024,13 +2024,13 @@
@Test
fun removeDetachedPointerInputFilters_middleWith3LeafsAllDetached_allLeafsRemoved() {
- val root = PointerInputFilterMock(isAttached = true)
+ val root = PointerInputFilterMock()
- val middle = PointerInputFilterMock(isAttached = true)
+ val middle = PointerInputFilterMock()
- val leaf1 = PointerInputFilterMock(isAttached = false)
- val leaf2 = PointerInputFilterMock(isAttached = false)
- val leaf3 = PointerInputFilterMock(isAttached = false)
+ val leaf1 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf2 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
+ val leaf3 = PointerInputFilterMock(layoutCoordinates = LayoutCoordinatesStub(false))
val pointerId1 = PointerId(3)
val pointerId2 = PointerId(5)
@@ -2913,6 +2913,440 @@
}
}
+ @Test
+ fun dispatchChanges_pifRemovesSelfDuringInitialDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.InitialDown
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovesSelfDuringPreUp_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreUp
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovesSelfDuringPreDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreDown
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovesSelfDuringPostUp_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostUp
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovesSelfDuringPostDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostDown
+ )
+ }
+
+ private fun dispatchChanges_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ removalPass: PointerEventPass
+ ) {
+ val layoutCoordinates = LayoutCoordinatesStub(true)
+ val pif: PointerInputFilter = PointerInputFilterMock(
+ pointerInputHandler =
+ spy(StubPointerInputHandler { changes, pass, _ ->
+ if (pass == removalPass) {
+ layoutCoordinates.isAttached = false
+ }
+ changes
+ }),
+ layoutCoordinates = layoutCoordinates
+ )
+ hitPathTracker.addHitPath(PointerId(13), listOf(pif))
+
+ hitPathTracker.dispatchChanges(listOf(down(13)))
+
+ var passedRemovalPass = false
+ PointerEventPass.values().forEach {
+ if (!passedRemovalPass) {
+ verify(pif).onPointerInput(any(), eq(it), any())
+ passedRemovalPass = it == removalPass
+ } else {
+ verify(pif, never()).onPointerInput(any(), eq(it), any())
+ }
+ }
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByParentDuringInitialDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.InitialDown
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByParentDuringPreUp_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreUp
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByParentDuringPreDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreDown
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByParentDuringPostUp_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostUp
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByParentDuringPostDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostDown
+ )
+ }
+
+ private fun dispatchChanges_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ removalPass: PointerEventPass
+ ) {
+ val childLayoutCoordinates = LayoutCoordinatesStub(true)
+ val parentPif: PointerInputFilter = PointerInputFilterMock(
+ pointerInputHandler =
+ spy(StubPointerInputHandler { changes, pass, _ ->
+ if (pass == removalPass) {
+ childLayoutCoordinates.isAttached = false
+ }
+ changes
+ })
+ )
+ val childPif: PointerInputFilter = PointerInputFilterMock(
+ layoutCoordinates = childLayoutCoordinates
+ )
+ hitPathTracker.addHitPath(PointerId(13), listOf(parentPif, childPif))
+
+ hitPathTracker.dispatchChanges(listOf(down(13)))
+
+ val removalPassIsDown =
+ when (removalPass) {
+ PointerEventPass.InitialDown -> true
+ PointerEventPass.PreDown -> true
+ PointerEventPass.PostDown -> true
+ else -> false
+ }
+ var passedRemovalPass = false
+ PointerEventPass.values().forEach {
+ passedRemovalPass = passedRemovalPass || removalPassIsDown && it == removalPass
+ if (!passedRemovalPass) {
+ verify(childPif).onPointerInput(any(), eq(it), any())
+ } else {
+ verify(childPif, never()).onPointerInput(any(), eq(it), any())
+ }
+ passedRemovalPass = passedRemovalPass || !removalPassIsDown && it == removalPass
+ }
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByChildDuringInitialDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.InitialDown
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByChildDuringPreUp_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreUp
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByChildDuringPreDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreDown
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByChildDuringPostUp_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostUp
+ )
+ }
+
+ @Test
+ fun dispatchChanges_pifRemovedByChildDuringPostDown_noPassesReceivedAfterwards() {
+ dispatchChanges_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostDown
+ )
+ }
+
+ private fun dispatchChanges_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ removalPass: PointerEventPass
+ ) {
+ val parentLayoutCoordinates = LayoutCoordinatesStub(true)
+ val parentPif: PointerInputFilter = PointerInputFilterMock(
+ layoutCoordinates = parentLayoutCoordinates
+ )
+ val childPif: PointerInputFilter = PointerInputFilterMock(
+ pointerInputHandler =
+ spy(StubPointerInputHandler { changes, pass, _ ->
+ if (pass == removalPass) {
+ parentLayoutCoordinates.isAttached = false
+ }
+ changes
+ })
+ )
+ hitPathTracker.addHitPath(PointerId(13), listOf(parentPif, childPif))
+
+ hitPathTracker.dispatchChanges(listOf(down(13)))
+
+ val removalPassIsDown =
+ when (removalPass) {
+ PointerEventPass.InitialDown -> true
+ PointerEventPass.PreDown -> true
+ PointerEventPass.PostDown -> true
+ else -> false
+ }
+ var passedRemovalPass = false
+ PointerEventPass.values().forEach {
+ passedRemovalPass = passedRemovalPass || !removalPassIsDown && it == removalPass
+ if (!passedRemovalPass) {
+ verify(parentPif).onPointerInput(any(), eq(it), any())
+ } else {
+ verify(parentPif, never()).onPointerInput(any(), eq(it), any())
+ }
+ passedRemovalPass = passedRemovalPass || removalPassIsDown && it == removalPass
+ }
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovesSelfDuringInitialDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.InitialDown
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovesSelfDuringPreUp_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreUp
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovesSelfDuringPreDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreDown
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovesSelfDuringPostUp_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostUp
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovesSelfDuringPostDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostDown
+ )
+ }
+
+ private fun dispatchCustomMessage_pifRemovesSelfDuringDispatch_noPassesReceivedAfterwards(
+ removalPass: PointerEventPass
+ ) {
+
+ lateinit var dispatcher: CustomEventDispatcher
+
+ val layoutCoordinates = LayoutCoordinatesStub(true)
+
+ val dispatchingPif = PointerInputFilterMock(initHandler = { dispatcher = it })
+ val receivingPif = PointerInputFilterMock(
+ _, pointerEventPass ->
+ if (pointerEventPass == removalPass) {
+ layoutCoordinates.isAttached = false
+ }
+ },
+ layoutCoordinates = layoutCoordinates
+ )
+
+ hitPathTracker.addHitPath(PointerId(13), listOf(dispatchingPif, receivingPif))
+
+ dispatcher.dispatchCustomEvent(object : CustomEvent {})
+
+ var passedRemovalPass = false
+ PointerEventPass.values().forEach {
+ if (!passedRemovalPass) {
+ verify(receivingPif).onCustomEvent(any(), eq(it))
+ passedRemovalPass = it == removalPass
+ } else {
+ verify(receivingPif, never()).onCustomEvent(any(), eq(it))
+ }
+ }
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByParentDuringInitialDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.InitialDown
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByParentDuringPreUp_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreUp
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByParentDuringPreDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreDown
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByParentDuringPostUp_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostUp
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByParentDuringPostDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostDown
+ )
+ }
+
+ private fun dispatchCustomMessage_pifRemovedByParentDuringDispatch_noPassesReceivedAfterwards(
+ removalPass: PointerEventPass
+ ) {
+ lateinit var dispatcher: CustomEventDispatcher
+
+ val layoutCoordinates = LayoutCoordinatesStub(true)
+
+ val dispatchingPif = PointerInputFilterMock(initHandler = { dispatcher = it })
+ val parentPif = PointerInputFilterMock(
+ _, pointerEventPass ->
+ if (pointerEventPass == removalPass) {
+ layoutCoordinates.isAttached = false
+ }
+ }
+ )
+ val childPif = PointerInputFilterMock(
+ layoutCoordinates = layoutCoordinates
+ )
+
+ hitPathTracker.addHitPath(PointerId(13), listOf(dispatchingPif, parentPif, childPif))
+
+ dispatcher.dispatchCustomEvent(object : CustomEvent {})
+
+ val removalPassIsDown =
+ when (removalPass) {
+ PointerEventPass.InitialDown -> true
+ PointerEventPass.PreDown -> true
+ PointerEventPass.PostDown -> true
+ else -> false
+ }
+ var passedRemovalPass = false
+ PointerEventPass.values().forEach {
+ passedRemovalPass = passedRemovalPass || removalPassIsDown && it == removalPass
+ if (!passedRemovalPass) {
+ verify(childPif).onCustomEvent(any(), eq(it))
+ } else {
+ verify(childPif, never()).onCustomEvent(any(), eq(it))
+ }
+ passedRemovalPass = passedRemovalPass || !removalPassIsDown && it == removalPass
+ }
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByChildDuringInitialDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.InitialDown
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByChildDuringPreUp_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreUp
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByChildDuringPreDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PreDown
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByChildDuringPostUp_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostUp
+ )
+ }
+
+ @Test
+ fun dispatchCustomMessage_pifRemovedByChildDuringPostDown_noPassesReceivedAfterwards() {
+ dispatchCustomMessage_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ PointerEventPass.PostDown
+ )
+ }
+
+ private fun dispatchCustomMessage_pifRemovedByChildDuringDispatch_noPassesReceivedAfterwards(
+ removalPass: PointerEventPass
+ ) {
+ lateinit var dispatcher: CustomEventDispatcher
+
+ val layoutCoordinates = LayoutCoordinatesStub(true)
+
+ val dispatchingPif = PointerInputFilterMock(initHandler = { dispatcher = it })
+ val parentPif = PointerInputFilterMock(
+ layoutCoordinates = layoutCoordinates
+ )
+ val childPif = PointerInputFilterMock(
+ _, pointerEventPass ->
+ if (pointerEventPass == removalPass) {
+ layoutCoordinates.isAttached = false
+ }
+ }
+ )
+
+ hitPathTracker.addHitPath(PointerId(13), listOf(dispatchingPif, parentPif, childPif))
+
+ dispatcher.dispatchCustomEvent(object : CustomEvent {})
+
+ val removalPassIsDown =
+ when (removalPass) {
+ PointerEventPass.InitialDown -> true
+ PointerEventPass.PreDown -> true
+ PointerEventPass.PostDown -> true
+ else -> false
+ }
+ var passedRemovalPass = false
+ PointerEventPass.values().forEach {
+ passedRemovalPass = passedRemovalPass || !removalPassIsDown && it == removalPass
+ if (!passedRemovalPass) {
+ verify(parentPif).onCustomEvent(any(), eq(it))
+ } else {
+ verify(parentPif, never()).onCustomEvent(any(), eq(it))
+ }
+ passedRemovalPass = passedRemovalPass || removalPassIsDown && it == removalPass
+ }
+ }
+
private fun areEqual(actualNode: NodeParent, expectedNode: NodeParent): Boolean {
var check = true
@@ -2960,20 +3394,23 @@
fun PointerInputFilterMock(
initHandler: (CustomEventDispatcher) -> Unit = mock(),
pointerInputHandler: PointerInputHandler = spy(StubPointerInputHandler()),
- isAttached: Boolean = true
+ layoutCoordinates: LayoutCoordinates = LayoutCoordinatesStub(true),
+ onCustomEvent: (CustomEvent, PointerEventPass) -> Unit = mock()
): PointerInputFilter =
spy(
PointerInputFilterStub(
pointerInputHandler,
- initHandler
+ initHandler,
+ onCustomEvent
).apply {
- layoutCoordinates = LayoutCoordinatesStub(isAttached)
+ this.layoutCoordinates = layoutCoordinates
}
)
open class PointerInputFilterStub(
val pointerInputHandler: PointerInputHandler,
- val initHandler: (CustomEventDispatcher) -> Unit
+ val initHandler: (CustomEventDispatcher) -> Unit,
+ val customEventHandler: (CustomEvent, PointerEventPass) -> Unit
) : PointerInputFilter() {
override fun onPointerInput(
@@ -2990,13 +3427,15 @@
initHandler(customEventDispatcher)
}
- override fun onCustomEvent(customEvent: CustomEvent, pass: PointerEventPass) {}
+ override fun onCustomEvent(customEvent: CustomEvent, pass: PointerEventPass) {
+ customEventHandler(customEvent, pass)
+ }
}
internal data class TestCustomEvent(val value: String) : CustomEvent
class LayoutCoordinatesStub(
- override val isAttached: Boolean = true
+ override var isAttached: Boolean = true
) : LayoutCoordinates {
override val size: IntPxSize