[go: nahoru, domu]

Merge "Passing MotionEvents all the way through." into androidx-master-dev
diff --git a/ui/ui-android-view/integration-tests/android-view-demos/src/main/AndroidManifest.xml b/ui/ui-android-view/integration-tests/android-view-demos/src/main/AndroidManifest.xml
index ae42839..ea84e16e 100644
--- a/ui/ui-android-view/integration-tests/android-view-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-android-view/integration-tests/android-view-demos/src/main/AndroidManifest.xml
@@ -42,6 +42,5 @@
             android:name=".ComposeScrollInAndroidScrollDifferentOrientation"
             android:configChanges="orientation|screenSize"
             android:label="ComposeScrollInAndroidScrollDifferentOrientation Activity" />
-
     </application>
 </manifest>
diff --git a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/AndroidViewDemos.kt b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/AndroidViewDemos.kt
index 8d539c6..86c9bf6 100644
--- a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/AndroidViewDemos.kt
+++ b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/AndroidViewDemos.kt
@@ -22,5 +22,6 @@
 val AndroidViewDemos = DemoCategory("AndroidView", listOf(
     ComposeInAndroidDemos,
     AndroidInComposeDemos,
+    ComplexTouchInterop,
     ActivityDemo("WebComponent", WebComponentActivity::class)
 ))
diff --git a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/ComplexInteractions.kt b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/ComplexInteractions.kt
index efccc1a..9225eb10 100644
--- a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/ComplexInteractions.kt
+++ b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/ComplexInteractions.kt
@@ -16,51 +16,102 @@
 
 package androidx.ui.androidview.demos
 
-import android.content.Context
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.widget.FrameLayout
+import android.widget.RelativeLayout
 import androidx.compose.Composable
 import androidx.compose.Recomposer
+import androidx.compose.foundation.Box
+import androidx.compose.state
+import androidx.ui.core.Alignment
 import androidx.ui.core.ContextAmbient
+import androidx.ui.core.Modifier
 import androidx.ui.core.setContent
 import androidx.ui.demos.common.ComposableDemo
 import androidx.ui.demos.common.DemoCategory
 import androidx.compose.foundation.Text
+import androidx.compose.foundation.background
+import androidx.ui.graphics.Color
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.ui.material.Button
+import androidx.ui.unit.dp
 import androidx.ui.viewinterop.AndroidView
 
 // TODO(b/158099918): Add this demo to AndroidViewDemos.kt once b/158099918 has been resolved.
 @Suppress("unused")
 val ComplexTouchInterop = DemoCategory("Complex Touch Interop", listOf(
-    ComposableDemo("ReproOffsetIssue") { ComposeInAndroidInComposeEtcTargetingDemo() }
+    ComposableDemo("Compose in Android in Compose in Android") {
+        ComposeInAndroidInComposeEtcTargetingDemo() }
 ))
 
 @Composable
 fun ComposeInAndroidInComposeEtcTargetingDemo() {
     val context = ContextAmbient.current
     Column {
-        Text("In this demo, there is a compose button inside Android, which is inside Compose, " +
-                "which inside Android... and on so on for a few times.  The conversions of " +
-                "pointer input events at every level still work.")
-        AndroidWithCompose(context) {
-            AndroidWithCompose(context) {
-                AndroidWithCompose(context) {
-                    Button( }) {
-                        Text("Click me")
-                    }
+        Text(
+            "In this demo, from the inside out, we have a Compose Button, wrapped in 2 Android " +
+                    "FrameLayouts, wrapped in a Compose Box, wrapped in a Column (which also has " +
+                    "this Text) which is then in the root AndroidComposeView."
+        )
+        Text(
+            "Each node in our tree affects the position of the button and the pointer input " +
+                    "events translate from Android to compose a couple of times and everything " +
+                    "still works."
+        )
+        Box(
+            Modifier
+                .fillMaxSize()
+                .background(color = Color(0xFF777777))
+                .padding(48.dp)
+        ) {
+
+            AndroidView(
+                modifier = Modifier.weight(1f),
+                view =
+                FrameLayout(context).apply {
+                    setPadding(100, 100, 100, 100)
+                    setBackgroundColor(0xFF888888.toInt())
+                    layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+                    addView(
+                        FrameLayout(context).apply {
+                            setPadding(100, 100, 100, 100)
+                            setBackgroundColor(0xFF999999.toInt())
+                            layoutParams =
+                                RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT).apply {
+                                    addRule(RelativeLayout.CENTER_IN_PARENT)
+                                }
+                            setContent(Recomposer.current()) {
+                                Box(
+                                    Modifier
+                                        .background(color = Color(0xFFAAAAAA))
+                                        .fillMaxSize()
+                                        .wrapContentSize(Alignment.Center)
+                                ) {
+                                    colorButton()
+                                }
+                            }
+                        }
+                    )
                 }
-            }
+            )
         }
     }
 }
 
 @Composable
-fun AndroidWithCompose(context: Context, children: @Composable () -> Unit) {
-    val anotherLayout = FrameLayout(context).also { view ->
-        view.setContent(Recomposer.current()) {
-            children()
+fun colorButton() {
+    val state = state { false }
+    val color =
+        if (state.value) {
+            Color.Red
+        } else {
+            Color.Blue
         }
-        view.setPadding(50, 50, 50, 50)
+    Button( state.value = !state.value }, backgroundColor = color) {
+        Text("Click me")
     }
-    AndroidView(anotherLayout)
 }
\ No newline at end of file
diff --git a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt
index c01c8b5..055e179 100644
--- a/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt
+++ b/ui/ui-android-view/integration-tests/android-view-demos/src/main/java/androidx/ui/androidview/demos/PointerInputInteropAndroidInCompose.kt
@@ -33,6 +33,7 @@
 import androidx.ui.geometry.Offset
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.preferredHeight
@@ -50,6 +51,9 @@
     },
     ComposableDemo("Android scroll in Compose scroll (same orientation)") {
         AndroidScrollInComposeScrollSameOrientation()
+    },
+    ComposableDemo("2 ScrollViews as separate children of Compose") {
+        TwoAndroidScrollViewsInCompose()
     }
 ))
 
@@ -108,10 +112,10 @@
         )
         Box(
             Modifier
-                .tapGestureFilter(onTap)
                 .fillMaxSize()
                 .wrapContentSize(Alignment.Center)
                 .preferredSize(240.dp)
+                .tapGestureFilter(onTap)
         ) {
             AndroidView(R.layout.android_tap_in_compose_tap) { view ->
                 theView = view
@@ -212,4 +216,19 @@
             }
         }
     }
+}
+
+@Composable
+private fun TwoAndroidScrollViewsInCompose() {
+    Column {
+        Text(
+            "Below are two Android Scrollviews that are nested in two different children of " +
+                    "Compose. The user should be able to scroll each independently at the same " +
+                    "time, but given that we currently don't split motion, this is not work."
+        )
+        Row {
+            AndroidView(R.layout.android_scrollview, Modifier.weight(2f))
+            AndroidView(R.layout.android_scrollview, Modifier.weight(1f))
+        }
+    }
 }
\ No newline at end of file
diff --git a/ui/ui-android-view/integration-tests/android-view-demos/src/main/res/layout/android_scrollview.xml b/ui/ui-android-view/integration-tests/android-view-demos/src/main/res/layout/android_scrollview.xml
new file mode 100644
index 0000000..73022bc
--- /dev/null
+++ b/ui/ui-android-view/integration-tests/android-view-demos/src/main/res/layout/android_scrollview.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<ScrollView android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:gravity="center">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="1" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="2" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="3" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="4" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="5" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="6" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="7" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="8" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="9" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="96sp"
+            android:text="10" />
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/ui/ui-core/api/0.1.0-dev16.txt b/ui/ui-core/api/0.1.0-dev16.txt
index 0c120f5..e8262d5 100644
--- a/ui/ui-core/api/0.1.0-dev16.txt
+++ b/ui/ui-core/api/0.1.0-dev16.txt
@@ -750,8 +750,6 @@
 
   public final class PointerEvent {
     ctor public PointerEvent(java.util.List<androidx.ui.core.PointerInputChange> changes);
-    method public java.util.List<androidx.ui.core.PointerInputChange> component1();
-    method public androidx.ui.core.PointerEvent copy(java.util.List<androidx.ui.core.PointerInputChange> changes);
     method public java.util.List<androidx.ui.core.PointerInputChange> getChanges();
   }
 
@@ -1858,12 +1856,7 @@
   public final class HitPathTrackerKt {
   }
 
-  public final class PointerInputEventData {
-    method public long component1();
-    method public androidx.ui.core.PointerInputData component2();
-    method public androidx.ui.core.pointerinput.PointerInputEventData copy-tII9_pY(long id, androidx.ui.core.PointerInputData pointerInputData);
-    method public long getId();
-    method public androidx.ui.core.PointerInputData getPointerInputData();
+  public final class MotionEventAdapterKt {
   }
 
   public final class PointerInputEventProcessorKt {
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 0c120f5..e8262d5 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -750,8 +750,6 @@
 
   public final class PointerEvent {
     ctor public PointerEvent(java.util.List<androidx.ui.core.PointerInputChange> changes);
-    method public java.util.List<androidx.ui.core.PointerInputChange> component1();
-    method public androidx.ui.core.PointerEvent copy(java.util.List<androidx.ui.core.PointerInputChange> changes);
     method public java.util.List<androidx.ui.core.PointerInputChange> getChanges();
   }
 
@@ -1858,12 +1856,7 @@
   public final class HitPathTrackerKt {
   }
 
-  public final class PointerInputEventData {
-    method public long component1();
-    method public androidx.ui.core.PointerInputData component2();
-    method public androidx.ui.core.pointerinput.PointerInputEventData copy-tII9_pY(long id, androidx.ui.core.PointerInputData pointerInputData);
-    method public long getId();
-    method public androidx.ui.core.PointerInputData getPointerInputData();
+  public final class MotionEventAdapterKt {
   }
 
   public final class PointerInputEventProcessorKt {
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
index 0c120f5..e8262d5 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
@@ -750,8 +750,6 @@
 
   public final class PointerEvent {
     ctor public PointerEvent(java.util.List<androidx.ui.core.PointerInputChange> changes);
-    method public java.util.List<androidx.ui.core.PointerInputChange> component1();
-    method public androidx.ui.core.PointerEvent copy(java.util.List<androidx.ui.core.PointerInputChange> changes);
     method public java.util.List<androidx.ui.core.PointerInputChange> getChanges();
   }
 
@@ -1858,12 +1856,7 @@
   public final class HitPathTrackerKt {
   }
 
-  public final class PointerInputEventData {
-    method public long component1();
-    method public androidx.ui.core.PointerInputData component2();
-    method public androidx.ui.core.pointerinput.PointerInputEventData copy-tII9_pY(long id, androidx.ui.core.PointerInputData pointerInputData);
-    method public long getId();
-    method public androidx.ui.core.PointerInputData getPointerInputData();
+  public final class MotionEventAdapterKt {
   }
 
   public final class PointerInputEventProcessorKt {
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 0c120f5..e8262d5 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -750,8 +750,6 @@
 
   public final class PointerEvent {
     ctor public PointerEvent(java.util.List<androidx.ui.core.PointerInputChange> changes);
-    method public java.util.List<androidx.ui.core.PointerInputChange> component1();
-    method public androidx.ui.core.PointerEvent copy(java.util.List<androidx.ui.core.PointerInputChange> changes);
     method public java.util.List<androidx.ui.core.PointerInputChange> getChanges();
   }
 
@@ -1858,12 +1856,7 @@
   public final class HitPathTrackerKt {
   }
 
-  public final class PointerInputEventData {
-    method public long component1();
-    method public androidx.ui.core.PointerInputData component2();
-    method public androidx.ui.core.pointerinput.PointerInputEventData copy-tII9_pY(long id, androidx.ui.core.PointerInputData pointerInputData);
-    method public long getId();
-    method public androidx.ui.core.PointerInputData getPointerInputData();
+  public final class MotionEventAdapterKt {
   }
 
   public final class PointerInputEventProcessorKt {
diff --git a/ui/ui-core/api/restricted_0.1.0-dev16.txt b/ui/ui-core/api/restricted_0.1.0-dev16.txt
index a6abfad..0780958 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev16.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev16.txt
@@ -802,8 +802,6 @@
 
   public final class PointerEvent {
     ctor public PointerEvent(java.util.List<androidx.ui.core.PointerInputChange> changes);
-    method public java.util.List<androidx.ui.core.PointerInputChange> component1();
-    method public androidx.ui.core.PointerEvent copy(java.util.List<androidx.ui.core.PointerInputChange> changes);
     method public java.util.List<androidx.ui.core.PointerInputChange> getChanges();
   }
 
@@ -1910,12 +1908,7 @@
   public final class HitPathTrackerKt {
   }
 
-  public final class PointerInputEventData {
-    method public long component1();
-    method public androidx.ui.core.PointerInputData component2();
-    method public androidx.ui.core.pointerinput.PointerInputEventData copy-tII9_pY(long id, androidx.ui.core.PointerInputData pointerInputData);
-    method public long getId();
-    method public androidx.ui.core.PointerInputData getPointerInputData();
+  public final class MotionEventAdapterKt {
   }
 
   public final class PointerInputEventProcessorKt {
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index a6abfad..0780958 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -802,8 +802,6 @@
 
   public final class PointerEvent {
     ctor public PointerEvent(java.util.List<androidx.ui.core.PointerInputChange> changes);
-    method public java.util.List<androidx.ui.core.PointerInputChange> component1();
-    method public androidx.ui.core.PointerEvent copy(java.util.List<androidx.ui.core.PointerInputChange> changes);
     method public java.util.List<androidx.ui.core.PointerInputChange> getChanges();
   }
 
@@ -1910,12 +1908,7 @@
   public final class HitPathTrackerKt {
   }
 
-  public final class PointerInputEventData {
-    method public long component1();
-    method public androidx.ui.core.PointerInputData component2();
-    method public androidx.ui.core.pointerinput.PointerInputEventData copy-tII9_pY(long id, androidx.ui.core.PointerInputData pointerInputData);
-    method public long getId();
-    method public androidx.ui.core.PointerInputData getPointerInputData();
+  public final class MotionEventAdapterKt {
   }
 
   public final class PointerInputEventProcessorKt {
diff --git a/ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/HitPathTrackerTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
similarity index 98%
rename from ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
rename to ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
index 9c94849..460f820 100644
--- a/ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/HitPathTrackerTest.kt
@@ -21,7 +21,6 @@
 import androidx.ui.core.Constraints
 import androidx.ui.core.CustomEvent
 import androidx.ui.core.CustomEventDispatcher
-import androidx.ui.core.InternalPointerEvent
 import androidx.ui.core.LayoutCoordinates
 import androidx.ui.core.PointerEventPass
 import androidx.ui.core.PointerId
@@ -30,11 +29,11 @@
 import androidx.ui.core.consumeDownChange
 import androidx.ui.core.consumePositionChange
 import androidx.ui.core.positionChange
+import androidx.ui.geometry.Offset
 import androidx.ui.testutils.down
 import androidx.ui.testutils.moveTo
 import androidx.ui.unit.IntSize
 import androidx.ui.unit.PxBounds
-import androidx.ui.geometry.Offset
 import androidx.ui.unit.milliseconds
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.any
@@ -208,7 +207,7 @@
 
     @Test
     fun dispatchChanges_noNodes_doesNotCrash() {
-        hitPathTracker.dispatchChanges(pointerEventOf(down(0)))
+        hitPathTracker.dispatchChanges(internalPointerEventOf(down(0)))
     }
 
     @Test
@@ -216,7 +215,7 @@
         val pif: PointerInputFilter = PointerInputFilterMock()
         hitPathTracker.addHitPath(PointerId(13), listOf(pif))
 
-        hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         // Verify call count
         verify(pif, times(5)).onPointerEvent(any(), any(), any())
@@ -237,7 +236,7 @@
         val pif3: PointerInputFilter = PointerInputFilterMock()
         hitPathTracker.addHitPath(PointerId(13), listOf(pif1, pif2, pif3))
 
-        hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         // Verify call count
         verify(pif1, times(5)).onPointerEvent(any(), any(), any())
@@ -270,7 +269,7 @@
         val pif3: PointerInputFilter = PointerInputFilterMock()
         hitPathTracker.addHitPath(PointerId(13), listOf(pif1, pif2, pif3))
 
-        hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         // Verify call count
         verify(pif1, times(5)).onPointerEvent(any(), any(), any())
@@ -323,7 +322,7 @@
         val event2 = down(5).moveTo(10.milliseconds, 7f, 9f)
 
         hitPathTracker.dispatchChanges(
-            pointerEventOf(event1, event2)
+            internalPointerEventOf(event1, event2)
         )
 
         // Verify call count
@@ -389,7 +388,7 @@
         val event2 = down(5).moveTo(10.milliseconds, 7f, 9f)
 
         hitPathTracker.dispatchChanges(
-            pointerEventOf(event1, event2)
+            internalPointerEventOf(event1, event2)
         )
 
         // Verify call count
@@ -462,7 +461,7 @@
         val event2 = down(5).moveTo(10.milliseconds, 7f, 9f)
 
         hitPathTracker.dispatchChanges(
-            pointerEventOf(event1, event2)
+            internalPointerEventOf(event1, event2)
         )
 
         // Verify call count
@@ -538,9 +537,9 @@
 
     @Test
     fun dispatchChanges_noNodes_nothingChanges() {
-        val (result, _) = hitPathTracker.dispatchChanges(pointerEventOf(down(5)))
+        val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(5)))
 
-        assertThat(result).isEqualTo(pointerEventOf(down(5)))
+        assertThat(result.changes.values.first()).isEqualTo(down(5))
     }
 
     @Test
@@ -553,9 +552,9 @@
         )
         hitPathTracker.addHitPath(PointerId(13), listOf(pif1))
 
-        val (result, _) = hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
-        assertThat(result).isEqualTo(pointerEventOf(down(13).consumeDownChange()))
+        assertThat(result.changes.values.first()).isEqualTo(down(13).consumeDownChange())
     }
 
     @Test
@@ -605,7 +604,7 @@
         hitPathTracker.addHitPath(PointerId(13), listOf(pif1, pif2, pif3))
         val change = down(13).moveTo(10.milliseconds, 0f, 130f)
 
-        val (result, _) = hitPathTracker.dispatchChanges(pointerEventOf(change))
+        val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(change))
 
         verify(pif1).onPointerInput(
             eq(listOf(change)), eq(PointerEventPass.InitialDown), any()
@@ -635,15 +634,8 @@
             eq(PointerEventPass.PreUp),
             any()
         )
-        assertThat(result)
-            .isEqualTo(
-                pointerEventOf(
-                    change.consumePositionChange(
-                        0f,
-                        126f
-                    )
-                )
-            ) // 2 + 4 + 8 + 16 + 32 + 64
+        assertThat(result.changes.values.first())
+            .isEqualTo(change.consumePositionChange(0f, 126f)) // 2 + 4 + 8 + 16 + 32 + 64
     }
 
     @Test
@@ -710,7 +702,7 @@
         val event2 = down(5).moveTo(10.milliseconds, 0f, -24f)
 
         val (result, _) = hitPathTracker.dispatchChanges(
-            pointerEventOf(event1, event2)
+            internalPointerEventOf(event1, event2)
         )
 
         verify(pif1).onPointerInput(
@@ -819,7 +811,7 @@
         val event2 = down(5).moveTo(10.milliseconds, 0f, -1000f)
 
         val (result, _) = hitPathTracker.dispatchChanges(
-            pointerEventOf(event1, event2)
+            internalPointerEventOf(event1, event2)
         )
 
         verify(parent).onPointerInput(
@@ -905,7 +897,7 @@
         val event2 = down(5).moveTo(10.milliseconds, 0f, -1000f)
 
         val (result, _) = hitPathTracker.dispatchChanges(
-            pointerEventOf(event1, event2)
+            internalPointerEventOf(event1, event2)
         )
 
         verify(child1).onPointerInput(
@@ -2669,7 +2661,7 @@
 
     @Test
     fun dispatchChanges_noNodes_reportsWasDispatchedToNothing() {
-        val (_, hitSomething) = hitPathTracker.dispatchChanges(pointerEventOf(down(0)))
+        val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(0)))
         assertThat(hitSomething).isFalse()
     }
 
@@ -2678,7 +2670,7 @@
         val pif: PointerInputFilter = PointerInputFilterMock()
         hitPathTracker.addHitPath(PointerId(13), listOf(pif))
 
-        val (_, hitSomething) = hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         assertThat(hitSomething).isTrue()
     }
@@ -2688,7 +2680,7 @@
         val pif: PointerInputFilter = PointerInputFilterMock()
         hitPathTracker.addHitPath(PointerId(13), listOf(pif))
 
-        val (_, hitSomething) = hitPathTracker.dispatchChanges(pointerEventOf(down(69)))
+        val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(69)))
 
         assertThat(hitSomething).isFalse()
     }
@@ -2965,7 +2957,7 @@
         )
         hitPathTracker.addHitPath(PointerId(13), listOf(pif))
 
-        hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         var passedRemovalPass = false
         PointerEventPass.values().forEach {
@@ -3031,7 +3023,7 @@
         )
         hitPathTracker.addHitPath(PointerId(13), listOf(parentPif, childPif))
 
-        hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         val removalPassIsDown =
             when (removalPass) {
@@ -3105,7 +3097,7 @@
         )
         hitPathTracker.addHitPath(PointerId(13), listOf(parentPif, childPif))
 
-        hitPathTracker.dispatchChanges(pointerEventOf(down(13)))
+        hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         val removalPassIsDown =
             when (removalPass) {
@@ -3471,7 +3463,4 @@
     override fun get(line: AlignmentLine): Int {
         TODO("not implemented")
     }
-}
-
-private fun pointerEventOf(vararg changes: PointerInputChange) =
-        InternalPointerEvent(changes.toList().associateBy { it.id }.toMutableMap())
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/MotionEventAdapterTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/MotionEventAdapterTest.kt
index 1e59127..4f64868 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/MotionEventAdapterTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/MotionEventAdapterTest.kt
@@ -31,8 +31,6 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
-// TODO(shepshapard): Not sure how to test if a MotionEvent has been recycled.
-
 @SmallTest
 @RunWith(JUnit4::class)
 class MotionEventAdapterTest {
@@ -58,7 +56,9 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
+        val platformEvent = pointerInputEvent.motionEvent
         assertThat(uptime.nanoseconds).isEqualTo(2_894_000_000L)
         assertThat(pointers).hasSize(1)
         assertPointerInputEventData(
@@ -68,6 +68,7 @@
             2967f,
             5928f
         )
+        assertThat(platformEvent).isSameInstanceAs(motionEvent)
     }
 
     @Test
@@ -94,7 +95,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(5_000_000L)
         assertThat(pointers).hasSize(1)
         assertPointerInputEventData(
@@ -130,7 +132,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(34_000_000L)
         assertThat(uptime.nanoseconds).isEqualTo(34_000_000L)
         assertThat(pointers).hasSize(1)
@@ -173,7 +176,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(4_000_000L)
         assertThat(pointers).hasSize(2)
         assertPointerInputEventData(
@@ -222,7 +226,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(4_000_000L)
         assertThat(pointers).hasSize(2)
         assertPointerInputEventData(
@@ -290,7 +295,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(12_000_000L)
         assertThat(pointers).hasSize(3)
         assertPointerInputEventData(
@@ -365,7 +371,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(12_000_000L)
         assertThat(pointers).hasSize(3)
         assertPointerInputEventData(
@@ -440,7 +447,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(12_000_000L)
         assertThat(pointers).hasSize(3)
         assertPointerInputEventData(
@@ -512,7 +520,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(10_000_000L)
         assertThat(pointers).hasSize(2)
         assertPointerInputEventData(
@@ -577,7 +586,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(10_000_000L)
         assertThat(pointers).hasSize(2)
         assertPointerInputEventData(
@@ -642,7 +652,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(10_000_000L)
         assertThat(pointers).hasSize(2)
         assertPointerInputEventData(
@@ -728,7 +739,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(20_000_000L)
         assertThat(pointers).hasSize(3)
         assertPointerInputEventData(
@@ -821,7 +833,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(20_000_000L)
         assertThat(pointers).hasSize(3)
         assertPointerInputEventData(
@@ -914,7 +927,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(20_000_000L)
         assertThat(pointers).hasSize(3)
         assertPointerInputEventData(
@@ -1102,7 +1116,8 @@
         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
         assertThat(pointerInputEvent).isNotNull()
 
-        val (uptime, pointers) = pointerInputEvent!!
+        val uptime = pointerInputEvent!!.uptime
+        val pointers = pointerInputEvent.pointers
         assertThat(uptime.nanoseconds).isEqualTo(0L)
         assertThat(pointers).hasSize(1)
         assertPointerInputEventData(pointers[0], PointerId(0), true, 1f, 2f)
@@ -1147,7 +1162,7 @@
         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
 
-        assertThat(motionEventAdapter.intIdToPointerIdMap).isEmpty()
+        assertThat(motionEventAdapter.motionEventToComposePointerIdMap).isEmpty()
     }
 
     @Test
@@ -1178,7 +1193,7 @@
         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
 
-        assertThat(motionEventAdapter.intIdToPointerIdMap).containsExactlyEntriesIn(
+        assertThat(motionEventAdapter.motionEventToComposePointerIdMap).containsExactlyEntriesIn(
             mapOf(
                 2 to PointerId(0),
                 5 to PointerId(1)
@@ -1229,7 +1244,7 @@
         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
 
-        assertThat(motionEventAdapter.intIdToPointerIdMap).containsExactlyEntriesIn(
+        assertThat(motionEventAdapter.motionEventToComposePointerIdMap).containsExactlyEntriesIn(
             mapOf(2 to PointerId(0))
         )
     }
@@ -1277,7 +1292,7 @@
         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
 
-        assertThat(motionEventAdapter.intIdToPointerIdMap).containsExactlyEntriesIn(
+        assertThat(motionEventAdapter.motionEventToComposePointerIdMap).containsExactlyEntriesIn(
             mapOf(5 to PointerId(1))
         )
     }
@@ -1334,7 +1349,7 @@
         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
         motionEventAdapter.convertToPointerInputEvent(motionEvent4)
 
-        assertThat(motionEventAdapter.intIdToPointerIdMap).isEmpty()
+        assertThat(motionEventAdapter.motionEventToComposePointerIdMap).isEmpty()
     }
 
     @Test
@@ -1379,7 +1394,7 @@
         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
 
-        assertThat(motionEventAdapter.intIdToPointerIdMap).isEmpty()
+        assertThat(motionEventAdapter.motionEventToComposePointerIdMap).isEmpty()
     }
 
     @Test
@@ -1399,6 +1414,77 @@
         assertThat(motionEvent.x).isEqualTo(13f)
         assertThat(motionEvent.y).isEqualTo(104f)
     }
+
+    @Test
+    fun convertToPointerInputEvent_1PointerActionDown_includesMotionEvent() {
+        val motionEvent = MotionEvent(
+            2894,
+            ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(8290)),
+            arrayOf(PointerCoords(2967f, 5928f))
+        )
+
+        val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
+        assertThat(pointerInputEvent).isNotNull()
+
+        assertThat(pointerInputEvent!!.motionEvent).isSameInstanceAs(motionEvent)
+    }
+
+    @Test
+    fun convertToPointerInputEvent_1pointerActionMove_includesMotionEvent() {
+        motionEventAdapter.convertToPointerInputEvent(
+            MotionEvent(
+                1,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(2)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        )
+        val motionEvent = MotionEvent(
+            5,
+            ACTION_MOVE,
+            1,
+            0,
+            arrayOf(PointerProperties(2)),
+            arrayOf(PointerCoords(6f, 7f))
+        )
+
+        val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
+        assertThat(pointerInputEvent).isNotNull()
+
+        assertThat(pointerInputEvent!!.motionEvent).isSameInstanceAs(motionEvent)
+    }
+
+    @Test
+    fun convertToPointerInputEvent_1pointerActionUp_includesMotionEvent() {
+        motionEventAdapter.convertToPointerInputEvent(
+            MotionEvent(
+                10,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(46)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        )
+        val motionEvent = MotionEvent(
+            34,
+            ACTION_UP,
+            1,
+            0,
+            arrayOf(PointerProperties(46)),
+            arrayOf(PointerCoords(3f, 4f))
+        )
+
+        val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
+        assertThat(pointerInputEvent).isNotNull()
+
+        assertThat(pointerInputEvent!!.motionEvent).isSameInstanceAs(motionEvent)
+    }
 }
 
 // Private helper functions
diff --git a/ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessorTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessorTest.kt
similarity index 97%
rename from ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessorTest.kt
rename to ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessorTest.kt
index 491bfa3..428c3e2 100644
--- a/ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessorTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessorTest.kt
@@ -17,9 +17,14 @@
 package androidx.ui.core.pointerinput
 
 import androidx.test.filters.SmallTest
+import androidx.ui.core.Constraints
 import androidx.ui.core.ConsumedData
 import androidx.ui.core.ExperimentalLayoutNodeApi
+import androidx.ui.core.LayoutDirection
 import androidx.ui.core.LayoutNode
+import androidx.ui.core.LayoutNodeWrapper
+import androidx.ui.core.Measurable
+import androidx.ui.core.MeasureScope
 import androidx.ui.core.Modifier
 import androidx.ui.core.Owner
 import androidx.ui.core.PointerEventPass
@@ -32,12 +37,15 @@
 import androidx.ui.unit.IntOffset
 import androidx.ui.unit.IntSize
 import androidx.ui.unit.Uptime
-import androidx.ui.unit.minus
 import androidx.ui.unit.milliseconds
+import androidx.ui.unit.minus
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.doAnswer
+import com.nhaarman.mockitokotlin2.doReturn
 import com.nhaarman.mockitokotlin2.eq
 import com.nhaarman.mockitokotlin2.inOrder
+import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.never
 import com.nhaarman.mockitokotlin2.reset
 import com.nhaarman.mockitokotlin2.spy
@@ -2650,13 +2658,15 @@
 }
 
 abstract class TestOwner : Owner {
-    var position = IntOffset.Origin
+    var position: IntOffset? = null
 
     @ExperimentalLayoutNodeApi
     override val root: LayoutNode
         get() = LayoutNode()
 
-    override fun calculatePosition() = position
+    override fun calculatePosition(): IntOffset {
+        return position ?: IntOffset.Origin
+    }
 }
 
 open class TestPointerInputFilter(
@@ -2675,4 +2685,45 @@
 }
 
 private class PointerInputModifierImpl(override val pointerInputFilter: PointerInputFilter) :
-    PointerInputModifier
\ No newline at end of file
+    PointerInputModifier
+
+@OptIn(ExperimentalLayoutNodeApi::class)
+private fun LayoutNode(x: Int, y: Int, x2: Int, y2: Int, modifier: Modifier = Modifier) =
+    LayoutNode().apply {
+        this.modifier = modifier
+        measureBlocks = object : LayoutNode.NoIntrinsicsMeasureBlocks("not supported") {
+            override fun measure(
+                measureScope: MeasureScope,
+                measurables: List<Measurable>,
+                constraints: Constraints,
+                layoutDirection: LayoutDirection
+            ): MeasureScope.MeasureResult =
+                measureScope.layout(x2 - x, y2 - y) {}
+        }
+        attach(mockOwner())
+        remeasure(Constraints(), layoutDirection)
+        var wrapper: LayoutNodeWrapper? = outerLayoutNodeWrapper
+        while (wrapper != null) {
+            wrapper.measureResult = innerLayoutNodeWrapper.measureResult
+            wrapper = (wrapper as? LayoutNodeWrapper)?.wrapped
+        }
+        place(x, y)
+        detach()
+    }
+
+@ExperimentalLayoutNodeApi
+private fun mockOwner(
+    position: IntOffset = IntOffset.Origin,
+    targetRoot: LayoutNode = LayoutNode()
+): Owner =
+    @Suppress("UNCHECKED_CAST")
+    mock {
+        on { calculatePosition() } doReturn position
+        on { root } doReturn targetRoot
+        on { observeMeasureModelReads(any(), any()) } doAnswer {
+            (it.arguments[1] as () -> Unit).invoke()
+        }
+        on { observeLayoutModelReads(any(), any()) } doAnswer {
+            (it.arguments[1] as () -> Unit).invoke()
+        }
+    }
\ No newline at end of file
diff --git a/ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/TestUtils.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/TestUtils.kt
similarity index 72%
rename from ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/TestUtils.kt
rename to ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/TestUtils.kt
index 3cb174d..a29b1e2 100644
--- a/ui/ui-core/src/test/kotlin/androidx/ui/core/pointerinput/TestUtils.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/pointerinput/TestUtils.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -16,6 +16,7 @@
 
 package androidx.ui.core.pointerinput
 
+import androidx.ui.core.InternalPointerEvent
 import androidx.ui.core.PointerEventPass
 import androidx.ui.core.PointerId
 import androidx.ui.core.PointerInputChange
@@ -32,7 +33,7 @@
  * the processing of incoming [PointerInputChange]s.
  */
 open class StubPointerInputHandler(
-    var modifyBlock: PointerInputHandler? = null
+    private var modifyBlock: PointerInputHandler? = null
 ) : PointerInputHandler {
     override fun invoke(
         p1: List<PointerInputChange>,
@@ -61,10 +62,20 @@
 ): PointerInputEvent {
     return PointerInputEvent(
         uptime,
-        listOf(PointerInputEventData(id, uptime, position, down))
+        listOf(PointerInputEventData(id, uptime, position, down)),
+        MotionEventDouble
     )
 }
 
+internal fun PointerInputEvent(
+    uptime: Uptime,
+    pointers: List<PointerInputEventData>
+) = PointerInputEvent(
+        uptime,
+        pointers,
+        MotionEventDouble
+    )
+
 internal fun catchThrowable(lambda: () -> Unit): Throwable? {
     var exception: Throwable? = null
 
@@ -75,4 +86,14 @@
     }
 
     return exception
-}
\ No newline at end of file
+}
+
+internal fun internalPointerEventOf(vararg changes: PointerInputChange) =
+    InternalPointerEvent(changes.toList().associateBy { it.id }.toMutableMap(), MotionEventDouble)
+
+/**
+ * To be used to construct types that require a MotionEvent but where no details of the MotionEvent
+ * are actually needed.
+ */
+private val MotionEventDouble = android.view.MotionEvent.obtain(0L, 0L,
+    android.view.MotionEvent.ACTION_DOWN, 0f, 0f, 0)
\ No newline at end of file
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropFilterIntegrationTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropFilterIntegrationTest.kt
new file mode 100644
index 0000000..15f5c6a
--- /dev/null
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropFilterIntegrationTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.node
+
+import android.content.Context
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_UP
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.compose.Recomposer
+import androidx.compose.foundation.Box
+import androidx.test.filters.MediumTest
+import androidx.ui.core.DensityAmbient
+import androidx.ui.core.setContent
+import androidx.ui.framework.test.TestActivity
+import androidx.ui.test.android.AndroidComposeTestRule
+import androidx.ui.unit.dp
+import androidx.ui.viewinterop.AndroidView
+import com.nhaarman.mockitokotlin2.clearInvocations
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+// Tests that pointer offsets are correct when a pointer is dispatched from Android through
+// Compose and back into Android and each layer offsets the pointer during dispatch.
+@MediumTest
+@RunWith(JUnit4::class)
+class PointerInteropFilterIntegrationTest {
+
+    private lateinit var five: View
+    private val theHitListener: () -> Unit = mock()
+
+    @get:Rule
+    val composeTestRule = AndroidComposeTestRule<TestActivity>()
+
+    @Before
+    fun setup() {
+        composeTestRule.activityRule.scenario.onActivity { activity ->
+
+            // one: Android View that is the touch target, inside
+            // two: Android View with 1x2 padding, inside
+            // three: Compose Box with 2x12 padding, inside
+            // four: Android View with 3x13 padding, inside
+            // five: Android View with 4x14 padding
+            //
+            // With all of the padding, "one" is at 10 x 50 relative to "five" and the tests
+            // dispatch MotionEvents to "five".
+
+            val  {
+                layoutParams = ViewGroup.LayoutParams(1, 1)
+                hitListener = theHitListener
+            }
+
+            val two = FrameLayout(activity).apply {
+                layoutParams = ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT
+                )
+                setPadding(1, 11, 0, 0)
+                addView(one)
+            }
+
+            val four = FrameLayout(activity).apply {
+                layoutParams = ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT
+                )
+                setPadding(3, 13, 0, 0)
+                setContent(Recomposer.current()) {
+                    with(DensityAmbient.current) {
+                        // Box is "three"
+                        Box(
+                            paddingStart = (2f / density).dp,
+                            paddingTop = (12f / density).dp
+                        ) {
+                            AndroidView(two)
+                        }
+                    }
+                }
+            }
+
+            five = FrameLayout(activity).apply {
+                layoutParams = ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT
+                )
+                setPadding(4, 14, 0, 0)
+                addView(four)
+            }
+
+            activity.setContentView(five)
+        }
+    }
+
+    @Test
+    fun uiClick_inside_hits() {
+        uiClick(10, 50, true)
+    }
+
+    @Test
+    fun uiClick_justOutside_misses() {
+        uiClick(9, 50, false)
+        uiClick(10, 49, false)
+        uiClick(11, 50, false)
+        uiClick(10, 51, false)
+    }
+
+    // Gets reused to should always clean up state.
+    private fun uiClick(x: Int, y: Int, hits: Boolean) {
+        clearInvocations(theHitListener)
+
+        composeTestRule.activityRule.scenario.onActivity {
+            val down =
+                MotionEvent.obtain(
+                    0L,
+                    0L,
+                    ACTION_DOWN,
+                    x.toFloat(),
+                    y.toFloat(),
+                    0
+                )
+            val up =
+                MotionEvent.obtain(
+                    0L,
+                    1L,
+                    ACTION_UP,
+                    x.toFloat(),
+                    y.toFloat(),
+                    0
+                )
+            five.dispatchTouchEvent(down)
+            five.dispatchTouchEvent(up)
+        }
+
+        if (hits) {
+            verify(theHitListener, times(2)).invoke()
+        } else {
+            verify(theHitListener, never()).invoke()
+        }
+    }
+}
+
+private class CustomView(context: Context) : View(context) {
+    lateinit var hitListener: () -> Unit
+
+    override fun onTouchEvent(event: MotionEvent?): Boolean {
+        hitListener()
+        return true
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropFilterTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropFilterTest.kt
index 2954242..db7a1c3 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropFilterTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropFilterTest.kt
@@ -20,20 +20,25 @@
 import android.view.MotionEvent
 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.MotionEvent.TOOL_TYPE_UNKNOWN
 import androidx.activity.ComponentActivity
 import androidx.test.filters.SmallTest
+import androidx.ui.core.PointerEvent
 import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
 import androidx.ui.core.consumeAllChanges
 import androidx.ui.core.consumeDownChange
 import androidx.ui.test.android.AndroidComposeTestRule
 import androidx.ui.testutils.consume
 import androidx.ui.testutils.down
-import androidx.ui.testutils.invokeOverAllPasses
-import androidx.ui.testutils.invokeOverPasses
 import androidx.ui.testutils.moveBy
 import androidx.ui.testutils.moveTo
 import androidx.ui.testutils.up
+import androidx.ui.unit.IntSize
 import androidx.ui.unit.milliseconds
 import androidx.ui.viewinterop.AndroidViewHolder
 import com.google.common.truth.Truth.assertThat
@@ -63,7 +68,7 @@
     // Verification of correct MotionEvents being dispatched (when no events are cancel)
 
     @Test
-    fun onPointerInput_1PointerDown_correctMotionEventDispatched() {
+    fun onPointerEvent_1PointerDown_correctMotionEventDispatched() {
         val down = down(1, 2.milliseconds, 3f, 4f)
         val expected =
             MotionEvent(
@@ -71,43 +76,67 @@
                 ACTION_DOWN,
                 1,
                 0,
-                arrayOf(PointerProperties(1)),
+                arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(3f, 4f))
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = expected)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[0], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[0]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_1PointerUp_correctMotionEventDispatched() {
+    fun onPointerEvent_1PointerUp_correctMotionEventDispatched() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val downMotionEvent =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val up = down.up(5.milliseconds)
         val expected =
             MotionEvent(
                 5,
-                MotionEvent.ACTION_UP,
+                ACTION_UP,
                 1,
                 0,
-                arrayOf(PointerProperties(1)),
+                arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = downMotionEvent)
+        )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(up)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(up, motionEvent = expected)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[1], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[1]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersDown_correctMotionEventDispatched() {
+    fun onPointerEvent_2PointersDown_correctMotionEventDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val downMotionEvent =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
@@ -115,12 +144,12 @@
         val expected =
             MotionEvent(
                 7,
-                MotionEvent.ACTION_POINTER_DOWN,
+                ACTION_POINTER_DOWN,
                 2,
                 1,
                 arrayOf(
-                    PointerProperties(1),
-                    PointerProperties(8)
+                    PointerProperties(0),
+                    PointerProperties(1)
                 ),
                 arrayOf(
                     PointerCoords(3f, 4f),
@@ -128,24 +157,37 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = downMotionEvent)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove, bDown, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[1], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[1]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersDownAllPassesAltOrder_correctMotionEventDispatched() {
+    fun onPointerEvent_2PointersDownAllPassesAltOrder_correctMotionEventDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val downMotionEvent =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
@@ -153,52 +195,61 @@
         val expected =
             MotionEvent(
                 7,
-                MotionEvent.ACTION_POINTER_DOWN,
+                ACTION_POINTER_DOWN,
                 2,
                 0,
                 arrayOf(
-                    PointerProperties(8),
-                    PointerProperties(1)
+                    PointerProperties(1),
+                    PointerProperties(0)
                 ),
                 arrayOf(
                     PointerCoords(10f, 11f),
                     PointerCoords(3f, 4f)
                 )
             )
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = downMotionEvent)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bDown, aMove)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bDown, aMove, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[1], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[1]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2Pointers1Up_correctMotionEventDispatched() {
+    fun onPointerEvent_2Pointers1Up_correctMotionEventDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
-
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
-        val bDown = down(8, 7.milliseconds, 10f, 11f)
-
-        val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
-        val bUp = bDown.up(13.milliseconds)
-
-        val expected =
+        val motionEvent1 =
             MotionEvent(
-                13,
-                MotionEvent.ACTION_POINTER_UP,
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
+        val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
                 2,
                 1,
                 arrayOf(
-                    PointerProperties(1),
-                    PointerProperties(8)
+                    PointerProperties(0),
+                    PointerProperties(1)
                 ),
                 arrayOf(
                     PointerCoords(3f, 4f),
@@ -206,40 +257,88 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-
-        // Act
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove2, bUp)
-
-        // Assert
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[2], expected)
-    }
-
-    @Test
-    fun onPointerInput_2Pointers1UpAllPassesAltOrder_correctMotionEventDispatched() {
-
-        // Arrange
-
-        val aDown = down(1, 2.milliseconds, 3f, 4f)
-
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
-        val bDown = down(8, 7.milliseconds, 10f, 11f)
-
         val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
         val bUp = bDown.up(13.milliseconds)
 
         val expected =
             MotionEvent(
                 13,
-                MotionEvent.ACTION_POINTER_UP,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(10f, 11f)
+                )
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+
+        // Act
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bUp, motionEvent = expected)
+        )
+
+        // Assert
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
+        assertThat(mockViewGroup.dispatchedMotionEvents[2]).isSameInstanceAs(expected)
+    }
+
+    @Test
+    fun onPointerEvent_2Pointers1UpAllPassesAltOrder_correctMotionEventDispatched() {
+
+        // Arrange
+
+        val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
+        val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(10f, 11f)
+                )
+            )
+
+        val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
+        val bUp = bDown.up(13.milliseconds)
+        val expected =
+            MotionEvent(
+                13,
+                ACTION_POINTER_UP,
                 2,
                 0,
                 arrayOf(
-                    PointerProperties(8),
+                    PointerProperties(0),
                     PointerProperties(1)
                 ),
                 arrayOf(
@@ -248,49 +347,92 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bUp, aMove2)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bUp, aMove2, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[2], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[2]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_1PointerMove_correctMotionEventDispatched() {
+    fun onPointerEvent_1PointerMove_correctMotionEventDispatched() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val move = down.moveTo(7.milliseconds, 8f, 9f)
         val expected =
             MotionEvent(
                 7,
-                MotionEvent.ACTION_MOVE,
+                ACTION_MOVE,
                 1,
                 0,
-                arrayOf(PointerProperties(1)),
+                arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(8f, 9f))
             )
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move, motionEvent = expected)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[1], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[1]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersMove_correctMotionEventDispatched() {
+    fun onPointerEvent_2PointersMove_correctMotionEventDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(11, 7.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                0,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(13f, 14f),
+                    PointerCoords(3f, 4f)
+                )
+            )
 
         val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
         val bMove1 = bDown.moveTo(15.milliseconds, 18f, 19f)
@@ -298,12 +440,12 @@
         val expected =
             MotionEvent(
                 15,
-                MotionEvent.ACTION_MOVE,
+                ACTION_MOVE,
                 2,
                 0,
                 arrayOf(
-                    PointerProperties(1),
-                    PointerProperties(11)
+                    PointerProperties(0),
+                    PointerProperties(1)
                 ),
                 arrayOf(
                     PointerCoords(8f, 9f),
@@ -311,40 +453,70 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove2, bMove1)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bMove1, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[2], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[2]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersMoveAltOrder_correctMotionEventDispatched() {
+    fun onPointerEvent_2PointersMoveAltOrder_correctMotionEventDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(11, 7.milliseconds, 13f, 14f)
-
-        val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
-        val bMove1 = bDown.moveTo(15.milliseconds, 18f, 19f)
-
-        val expected =
+        val motionEvent2 =
             MotionEvent(
-                15,
-                MotionEvent.ACTION_MOVE,
+                7,
+                ACTION_POINTER_DOWN,
                 2,
                 0,
                 arrayOf(
-                    PointerProperties(11),
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(13f, 14f),
+                    PointerCoords(3f, 4f)
+                )
+            )
+
+        val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
+        val bMove1 = bDown.moveTo(15.milliseconds, 18f, 19f)
+        val expected =
+            MotionEvent(
+                15,
+                ACTION_MOVE,
+                2,
+                0,
+                arrayOf(
+                    PointerProperties(0),
                     PointerProperties(1)
                 ),
                 arrayOf(
@@ -353,24 +525,40 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bMove1, aMove2)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bMove1, aMove2, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[2], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[2]).isSameInstanceAs(expected)
     }
 
     // Verification of correct cancel events being dispatched
 
     @Test
-    fun onPointerInput_1PointerUpConsumed_correctCancelDispatched() {
+    fun onPointerEvent_1PointerUpConsumed_correctCancelDispatched() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val upConsumed = down.up(5.milliseconds).consumeDownChange()
         val expected =
             MotionEvent(
@@ -378,27 +566,39 @@
                 ACTION_CANCEL,
                 1,
                 0,
-                arrayOf(PointerProperties(1)),
+                arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(upConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(upConsumed, motionEvent = expected)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[1], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[1]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersDown2ndDownConsumed_correctCancelDispatched() {
+    fun onPointerEvent_2PointersDown2ndDownConsumed_correctCancelDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDownConsumed = down(8, 7.milliseconds, 10f, 11f).consumeDownChange()
-
         val expected =
             MotionEvent(
                 7,
@@ -406,8 +606,8 @@
                 2,
                 0,
                 arrayOf(
-                    PointerProperties(1),
-                    PointerProperties(8)
+                    PointerProperties(0),
+                    PointerProperties(1)
                 ),
                 arrayOf(
                     PointerCoords(3f, 4f),
@@ -415,32 +615,58 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove, bDownConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove, bDownConsumed, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[1], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[1]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2Pointers1UpConsumed_correctCancelDispatched() {
+    fun onPointerEvent_2Pointers1UpConsumed_correctCancelDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(10f, 11f)
+                )
+            )
 
         val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
         val bUpConsumed = bDown.up(13.milliseconds).consumeDownChange()
-
         val expected =
             MotionEvent(
                 13,
@@ -448,8 +674,8 @@
                 2,
                 0,
                 arrayOf(
-                    PointerProperties(1),
-                    PointerProperties(8)
+                    PointerProperties(0),
+                    PointerProperties(1)
                 ),
                 arrayOf(
                     PointerCoords(3f, 4f),
@@ -457,23 +683,38 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove2, bUpConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bUpConsumed, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[2], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[2]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_1PointerMoveConsumed_correctCancelDispatched() {
+    fun onPointerEvent_1PointerMoveConsumed_correctCancelDispatched() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val moveConsumed = down.moveTo(7.milliseconds, 8f, 9f).consume(1f, 0f)
         val expected =
             MotionEvent(
@@ -481,26 +722,54 @@
                 ACTION_CANCEL,
                 1,
                 0,
-                arrayOf(PointerProperties(1)),
-                arrayOf(PointerCoords(8f, 9f))
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
             )
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(moveConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(moveConsumed, motionEvent = expected)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[1], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[1]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersMoveConsumed_correctCancelDispatched() {
+    fun onPointerEvent_2PointersMoveConsumed_correctCancelDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(11, 7.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(13f, 14f)
+                )
+            )
 
         val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
         val bMoveConsumed = bDown.moveTo(15.milliseconds, 18f, 19f).consume(1f, 0f)
@@ -512,8 +781,8 @@
                 2,
                 0,
                 arrayOf(
-                    PointerProperties(1),
-                    PointerProperties(11)
+                    PointerProperties(0),
+                    PointerProperties(1)
                 ),
                 arrayOf(
                     PointerCoords(8f, 9f),
@@ -521,131 +790,399 @@
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove2, bMoveConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bMoveConsumed, motionEvent = expected)
+        )
 
         // Assert
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[2], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[2]).isSameInstanceAs(expected)
     }
 
     // Verification of no longer dispatching to children once we have consumed events
 
     @Test
-    fun onPointerInput_downConsumed_nothingDispatched() {
+    fun onPointerEvent_downConsumed_nothingDispatched() {
         val downConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(downConsumed)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(downConsumed, motionEvent = motionEvent1)
+        )
+
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_downConsumedThenMoveThenUp_nothingDispatched() {
+    fun onPointerEvent_downConsumedThenMoveThenUp_nothingDispatched() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         val aMove = aDownConsumed.moveTo(5.milliseconds, 6f, 7f)
-        val aUp = aMove.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 7f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp)
+        val aUp = aMove.up(10.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                10,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 7f))
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, motionEvent = motionEvent3)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_down1ConsumedThenDown2ThenMove2ThenUp2_nothingDispatched() {
+    fun onPointerEvent_down1ConsumedThenDown2ThenMove2ThenUp2_nothingDispatched() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
-        val aMove1 = aDownConsumed.moveTo(5.milliseconds, 6f, 7f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        val aMove1 = aDownConsumed.moveTo(5.milliseconds, 3f, 4f)
         val bDown = down(11, 5.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
+
         val aMove2 = aDownConsumed.moveTo(21.milliseconds, 6f, 7f)
         val bMove = bDown.moveTo(21.milliseconds, 22f, 23f)
+        val motionEvent3 =
+            MotionEvent(
+                21,
+                ACTION_MOVE,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(6f, 7f), PointerCoords(22f, 23f))
+            )
+
         val aMove3 = aDownConsumed.moveTo(31.milliseconds, 6f, 7f)
         val bUp = bMove.up(31.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(6f, 7f), PointerCoords(22f, 23f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove2, bMove)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove3, bUp)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove3, bUp, motionEvent = motionEvent4)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_down1ConsumedThenDown2ThenUp1ThenDown3_nothingDispatched() {
+    fun onPointerEvent_down1ConsumedThenDown2ThenUp1ThenDown3_nothingDispatched() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDownConsumed.moveTo(11.milliseconds, 3f, 4f)
+        val aMove1 = aDownConsumed.moveTo(22.milliseconds, 3f, 4f)
         val bDown = down(21, 22.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                22,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aUp = aMove1.up(31.milliseconds)
         val bMove1 = bDown.moveTo(31.milliseconds, 23f, 24f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val bMove2 = bMove1.moveTo(41.milliseconds, 23f, 24f)
         val cDown = down(51, 41.milliseconds, 52f, 53f)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(1), PointerProperties(0)),
+                arrayOf(PointerCoords(23f, 24f), PointerCoords(52f, 53f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp, bMove1)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bMove2, cDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, bMove1, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bMove2, cDown, motionEvent = motionEvent4)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_downThenMoveConsumedThenMoveThenUp_afterConsumeNoDispatch() {
+    fun onPointerEvent_downThenMoveConsumedThenMoveThenUp_afterConsumeNoDispatch() {
         val down = down(1, 2.milliseconds, 3f, 4f)
-        val move1Consumed = down.moveTo(5.milliseconds, 6f, 7f).consume(0f, 1f)
-        val move2 = move1Consumed.moveTo(10.milliseconds, 11f, 12f)
-        val up = move2.up(10.milliseconds)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move1Consumed)
+        val move1Consumed = down.moveTo(5.milliseconds, 6f, 7f).consume(0f, 1f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 47f))
+            )
+        val move2 = move1Consumed.moveTo(10.milliseconds, 11f, 12f)
+        val motionEvent3 =
+            MotionEvent(
+                10,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(11f, 12f))
+            )
+        val up = move2.up(15.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                15,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(11f, 12f))
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move1Consumed, motionEvent = motionEvent2)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move2)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(up)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move2, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(up, motionEvent = motionEvent4)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_down1ThenDown2ConsumedThenMoveThenUp1ThenUp2_afterConsumeNoDispatch() {
+    fun onPointerEvent_down1ThenDown2ConsumedThenMoveThenUp1ThenUp2_afterConsumeNoDispatch() {
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
         val bDownConsumed = down(21, 11.milliseconds, 23f, 24f).consumeDownChange()
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aMove2 = aMove1.moveTo(31.milliseconds, 31f, 32f)
         val bMove = bDownConsumed.moveTo(31.milliseconds, 33f, 34f)
+        val motionEvent3 =
+            MotionEvent(
+                11,
+                ACTION_MOVE,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(31f, 32f), PointerCoords(33f, 34f))
+            )
 
-        val aMove3 = aMove2.moveTo(41.milliseconds, 42f, 43f)
+        val aMove3 = aMove2.moveTo(41.milliseconds, 31f, 32f)
         val bUp = bMove.up(41.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(31f, 32f), PointerCoords(33f, 34f))
+            )
 
         val aUp = aMove3.up(51.milliseconds)
+        val motionEvent5 =
+            MotionEvent(
+                51,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(31f, 32f))
+            )
 
         // Act
-        val pointerInputHandler = pointerInteropFilter.pointerInputFilter::onPointerInput
-        pointerInputHandler.invokeOverAllPasses(aDown)
-        pointerInputHandler.invokeOverAllPasses(aMove1, bDownConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDownConsumed, motionEvent = motionEvent2)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInputHandler.invokeOverAllPasses(aMove2, bMove)
-        pointerInputHandler.invokeOverAllPasses(aMove3, bUp)
-        pointerInputHandler.invokeOverAllPasses(aUp)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove3, bUp, motionEvent = motionEvent4)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, motionEvent = motionEvent5)
+        )
 
         // Assert
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_down1ConsumedThenUp1ThenDown2_finalDownDispatched() {
+    fun onPointerEvent_down1ConsumedThenUp1ThenDown2_finalDownDispatched() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         val aUp = aDownConsumed.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(
+                    PointerProperties(0)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f)
+                )
+            )
+
         val bDown = down(11, 12.milliseconds, 13f, 14f)
         val expected =
             MotionEvent(
@@ -654,109 +1191,236 @@
                 1,
                 0,
                 arrayOf(
-                    PointerProperties(11)
+                    PointerProperties(0)
                 ),
                 arrayOf(
                     PointerCoords(13f, 14f)
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bDown, motionEvent = expected)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[0], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[0]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_down1ConsumedThenDown2ThenUp1ThenUp2ThenDown3_finalDownDispatched() {
+    fun onPointerEvent_down1ConsumedThenDown2ThenUp1ThenUp2ThenDown3_finalDownDispatched() {
 
         // Arrange
 
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDownConsumed.moveTo(22.milliseconds, 3f, 4f)
         val bDown = down(21, 22.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                22,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aUp = aMove1.up(31.milliseconds)
         val bMove1 = bDown.moveTo(31.milliseconds, 23f, 24f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val bUp = bMove1.up(41.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(1)),
+                arrayOf(PointerCoords(23f, 24f))
+            )
 
         val cDown = down(51, 52.milliseconds, 53f, 54f)
-
         val expected =
             MotionEvent(
                 52,
                 ACTION_DOWN,
                 1,
                 0,
-                arrayOf(
-                    PointerProperties(51)
-                ),
-                arrayOf(
-                    PointerCoords(53f, 54f)
-                )
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(53f, 54f))
             )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp, bMove1)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bUp)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(cDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, bMove1, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bUp, motionEvent = motionEvent4)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(cDown, motionEvent = expected)
+        )
 
         // Assert
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[0], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[0]).isSameInstanceAs(expected)
     }
 
     // Verification no longer dispatching to children due to the child returning false for
     // dispatchTouchEvent(...)
 
     @Test
-    fun onPointerInput_downViewRetsFalseThenMoveThenUp_noDispatchAfterRetFalse() {
+    fun onPointerEvent_downViewRetsFalseThenMoveThenUp_noDispatchAfterRetFalse() {
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         val aMove = aDown.moveTo(5.milliseconds, 6f, 7f)
-        val aUp = aMove.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                2,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 7f))
+            )
+
+        val aUp = aMove.up(10.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                10,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 7f))
+            )
+
         mockViewGroup.returnValue = false
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, motionEvent = motionEvent3)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_down1ViewRetsFalseThenDown2ThenMove2ThenUp2_noDispatchAfterRetFalse() {
+    fun onPointerEvent_down1ViewRetsFalseThenDown2ThenMove2ThenUp2_noDispatchAfterRetFalse() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(5.milliseconds, 6f, 7f)
+        val aMove1 = aDown.moveTo(5.milliseconds, 3f, 4f)
         val bDown = down(11, 5.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
 
         val aMove2 = aDown.moveTo(21.milliseconds, 6f, 7f)
         val bMove = bDown.moveTo(21.milliseconds, 22f, 23f)
+        val motionEvent3 =
+            MotionEvent(
+                21,
+                ACTION_MOVE,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(6f, 7f), PointerCoords(22f, 23f))
+            )
 
         val aMove3 = aDown.moveTo(31.milliseconds, 6f, 7f)
         val bUp = bMove.up(31.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(6f, 7f), PointerCoords(22f, 23f))
+            )
 
         mockViewGroup.returnValue = false
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove2, bMove)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove3, bUp)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove3, bUp, motionEvent = motionEvent4)
+        )
 
         // Assert
 
@@ -764,81 +1428,246 @@
     }
 
     @Test
-    fun onPointerInput_down1ViewRetsFalseThenDown2ThenUp1ThenDown3_noDispatchAfterRetFalse() {
+    fun onPointerEvent_down1ViewRetsFalseThenDown2ThenUp1ThenDown3_noDispatchAfterRetFalse() {
         val aDown = down(1, 2.milliseconds, 3f, 4f)
-
-        val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
-        val bDown = down(21, 22.milliseconds, 23f, 24f)
-
-        val aUp = aMove1.up(31.milliseconds)
-        val bMove1 = bDown.moveTo(31.milliseconds, 23f, 24f)
-
-        val bMove2 = bMove1.moveTo(41.milliseconds, 23f, 24f)
-        val cDown = down(51, 41.milliseconds, 52f, 53f)
-
-        mockViewGroup.returnValue = false
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp, bMove1)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bMove2, cDown)
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
-    }
-
-    @Test
-    fun onPointerInput_downThenMoveViewRetsFalseThenMoveThenUp_noDispatchAfterRetFalse() {
-        val down = down(1, 2.milliseconds, 3f, 4f)
-        val move1 = down.moveTo(5.milliseconds, 6f, 7f)
-        val move2 = move1.moveTo(10.milliseconds, 11f, 12f)
-        val up = move2.up(10.milliseconds)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-        mockViewGroup.returnValue = false
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move1)
-        mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move2)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(up)
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
-    }
-
-    @Test
-    fun onPointerInput_down1ThenDown2ViewRetsFalseThenMoveThenUp1ThenUp2_noDispatchAfterRetFalse() {
-        // Arrange
-
-        val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
         val bDown = down(21, 11.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_POINTER_DOWN,
+                2,
+                2,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
+
+        val aUp = aMove1.up(31.milliseconds)
+        val bMove1 = bDown.moveTo(31.milliseconds, 23f, 24f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
+
+        val bMove2 = bMove1.moveTo(41.milliseconds, 23f, 24f)
+        val cDown = down(51, 41.milliseconds, 52f, 53f)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(1), PointerProperties(0)),
+                arrayOf(PointerCoords(23f, 24f), PointerCoords(52f, 53f))
+            )
+
+        mockViewGroup.returnValue = false
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        mockViewGroup.dispatchedMotionEvents.clear()
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, bMove1, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bMove2, cDown, motionEvent = motionEvent4)
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
+    }
+
+    @Test
+    fun onPointerEvent_downThenMoveViewRetsFalseThenMoveThenUp_noDispatchAfterRetFalse() {
+        val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        val move1 = down.moveTo(5.milliseconds, 6f, 7f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 47f))
+            )
+
+        val move2 = move1.moveTo(10.milliseconds, 11f, 12f)
+        val motionEvent3 =
+            MotionEvent(
+                10,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(11f, 12f))
+            )
+
+        val up = move2.up(15.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                15,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(11f, 12f))
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+        mockViewGroup.returnValue = false
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move1, motionEvent = motionEvent2)
+        )
+        mockViewGroup.dispatchedMotionEvents.clear()
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move2, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(up, motionEvent = motionEvent4)
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
+    }
+
+    @Test
+    fun onPointerEvent_down1ThenDown2ViewRetsFalseThenMoveThenUp1ThenUp2_noDispatchAfterRetFalse() {
+        // Arrange
+
+        val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
+        val bDown = down(21, 11.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_POINTER_DOWN,
+                2,
+                2,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aMove2 = aMove1.moveTo(31.milliseconds, 31f, 32f)
         val bMove = bDown.moveTo(31.milliseconds, 33f, 34f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_MOVE,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(31f, 32f), PointerCoords(33f, 34f))
+            )
 
-        val aMove3 = aMove2.moveTo(41.milliseconds, 42f, 43f)
+        val aMove3 = aMove2.moveTo(41.milliseconds, 31f, 32f)
         val bUp = bMove.up(41.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(31f, 32f), PointerCoords(33f, 34f))
+            )
 
         val aUp = aMove3.up(51.milliseconds)
+        val motionEvent5 =
+            MotionEvent(
+                51,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(31f, 32f))
+            )
 
         // Act
-        val pointerInputHandler = pointerInteropFilter.pointerInputFilter::onPointerInput
-        pointerInputHandler.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = false
-        pointerInputHandler.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInputHandler.invokeOverAllPasses(aMove2, bMove)
-        pointerInputHandler.invokeOverAllPasses(aMove3, bUp)
-        pointerInputHandler.invokeOverAllPasses(aUp)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove3, bUp, motionEvent = motionEvent4)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, motionEvent = motionEvent5)
+        )
 
         // Assert
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
     }
 
     @Test
-    fun onPointerInput_down1ViewRetsFalseThenUp1ThenDown2_finalDownDispatched() {
+    fun onPointerEvent_down1ViewRetsFalseThenUp1ThenDown2_finalDownDispatched() {
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         val aUp = aDown.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         val bDown = down(11, 12.milliseconds, 13f, 14f)
         mockViewGroup.returnValue = false
         val expected =
@@ -848,67 +1677,113 @@
                 1,
                 0,
                 arrayOf(
-                    PointerProperties(11)
+                    PointerProperties(0)
                 ),
                 arrayOf(
                     PointerCoords(13f, 14f)
                 )
             )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bDown, motionEvent = expected)
+        )
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[0], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[0]).isSameInstanceAs(expected)
     }
 
     @Test
-    fun onPointerInput_down1ViewRetsFalseThenDown2ThenUp1ThenUp2ThenDown3_finalDownDispatched() {
+    fun onPointerEvent_down1ViewRetsFalseThenDown2ThenUp1ThenUp2ThenDown3_finalDownDispatched() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
         val bDown = down(21, 11.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aUp = aMove1.up(31.milliseconds)
         val bMove1 = bDown.moveTo(31.milliseconds, 23f, 24f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val bUp = bMove1.up(41.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(1)),
+                arrayOf(PointerCoords(23f, 24f))
+            )
 
         val cDown = down(51, 52.milliseconds, 53f, 54f)
-
-        mockViewGroup.returnValue = false
-
         val expected =
             MotionEvent(
                 52,
                 ACTION_DOWN,
                 1,
                 0,
-                arrayOf(
-                    PointerProperties(51)
-                ),
-                arrayOf(
-                    PointerCoords(53f, 54f)
-                )
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(53f, 54f))
             )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        mockViewGroup.returnValue = false
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp, bMove1)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bUp)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(cDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, bMove1, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bUp, motionEvent = motionEvent4)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(cDown, motionEvent = expected)
+        )
 
         // Assert
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-        assertEquals(mockViewGroup.dispatchedMotionEvents[0], expected)
+        assertThat(mockViewGroup.dispatchedMotionEvents[0]).isSameInstanceAs(expected)
     }
 
     // Verification of correct consumption due to the return value of View.dispatchTouchEvent(...).
@@ -916,75 +1791,166 @@
     // be consumed should be consumed.
 
     @Test
-    fun onPointerInput_1PointerDownViewRetsFalse_nothingConsumed() {
+    fun onPointerEvent_1PointerDownViewRetsFalse_nothingConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         mockViewGroup.returnValue = false
 
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(down, motionEvent = motionEvent1)
+            )
 
-        assertThat(actual).isEqualTo(down)
+        assertThat(actual.first()).isEqualTo(down)
     }
 
     @Test
-    fun onPointerInput_1PointerDownViewRetsTrue_everythingConsumed() {
+    fun onPointerEvent_1PointerDownViewRetsTrue_everythingConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         mockViewGroup.returnValue = true
         val expected = down.consumeAllChanges()
 
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(down, motionEvent = motionEvent1)
+            )
 
-        assertThat(actual).isEqualTo(expected)
+        assertThat(actual.first()).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_1PointerUpViewRetsFalse_nothingConsumed() {
+    fun onPointerEvent_1PointerUpViewRetsFalse_nothingConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val up = down.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         mockViewGroup.returnValue = true
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
         mockViewGroup.returnValue = false
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(up)
+        val actual = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(up, motionEvent = motionEvent2)
+        )
 
-        assertThat(actual).isEqualTo(up)
+        assertThat(actual.first()).isEqualTo(up)
     }
 
     @Test
-    fun onPointerInput_1PointerUpViewRetsTrue_everythingConsumed() {
+    fun onPointerEvent_1PointerUpViewRetsTrue_everythingConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val up = down.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         val expected = up.consumeAllChanges()
         mockViewGroup.returnValue = true
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(up)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(up, motionEvent = motionEvent2)
+            )
 
-        assertThat(actual).isEqualTo(expected)
+        assertThat(actual.first()).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersDownViewRetsFalse_nothingConsumed() {
+    fun onPointerEvent_2PointersDownViewRetsFalse_nothingConsumed() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         mockViewGroup.returnValue = true
 
         val expected = listOf(aMove, bDown)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
 
         // Act
 
         mockViewGroup.returnValue = false
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove, bDown)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove, bDown, motionEvent = motionEvent2)
+            )
 
         // Assert
 
@@ -992,25 +1958,47 @@
     }
 
     @Test
-    fun onPointerInput_2PointersDownViewRetsTrue_everythingConsumed() {
+    fun onPointerEvent_2PointersDownViewRetsTrue_everythingConsumed() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         mockViewGroup.returnValue = true
 
         val expected = listOf(aMove.consumeAllChanges(), bDown.consumeAllChanges())
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
 
         // Act
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove, bDown)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove, bDown, motionEvent = motionEvent2)
+            )
 
         // Assert
 
@@ -1018,30 +2006,63 @@
     }
 
     @Test
-    fun onPointerInput_2Pointers1UpViewRetsFalse_nothingConsumed() {
+    fun onPointerEvent_2Pointers1UpViewRetsFalse_nothingConsumed() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
         val bUp = bDown.up(13.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                13,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         mockViewGroup.returnValue = true
 
         val expected = listOf(aMove2, bUp)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
         mockViewGroup.returnValue = false
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove2, bUp)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bUp, motionEvent = motionEvent3)
+            )
 
         // Assert
 
@@ -1049,29 +2070,62 @@
     }
 
     @Test
-    fun onPointerInput_2Pointers1UpViewRetsTrue_everythingConsumed() {
+    fun onPointerEvent_2Pointers1UpViewRetsTrue_everythingConsumed() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
         val bUp = bDown.up(13.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                13,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         mockViewGroup.returnValue = true
 
         val expected = listOf(aMove2.consumeAllChanges(), bUp.consumeAllChanges())
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove2, bUp)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bUp, motionEvent = motionEvent3)
+            )
 
         // Assert
 
@@ -1079,58 +2133,147 @@
     }
 
     @Test
-    fun onPointerInput_1PointerMoveViewRetsFalse_nothingConsumed() {
+    fun onPointerEvent_1PointerMoveViewRetsFalse_nothingConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val move = down.moveTo(7.milliseconds, 8f, 9f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(8f, 9f))
+            )
         mockViewGroup.returnValue = true
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
         mockViewGroup.returnValue = false
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(move, motionEvent = motionEvent2)
+            )
 
-        assertThat(actual).isEqualTo(move)
+        assertThat(actual.first()).isEqualTo(move)
     }
 
     @Test
-    fun onPointerInput_1PointerMoveViewRetsTrue_everythingConsumed() {
+    fun onPointerEvent_1PointerMoveViewRetsTrue_everythingConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
-        val move = down.moveBy(7.milliseconds, 8f, 9f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val move = down.moveTo(7.milliseconds, 8f, 9f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(8f, 9f))
+            )
         mockViewGroup.returnValue = true
         val expected = move.consumeAllChanges()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(move, motionEvent = motionEvent2)
+            )
 
-        assertThat(actual).isEqualTo(expected)
+        assertThat(actual.first()).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersMoveViewRetsFalse_nothingConsumed() {
+    fun onPointerEvent_2PointersMoveViewRetsFalse_nothingConsumed() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(11, 7.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(13f, 14f)
+                )
+            )
 
         val aMove2 = aMove1.moveBy(15.milliseconds, 8f, 9f)
         val bMove1 = bDown.moveBy(15.milliseconds, 18f, 19f)
+        val motionEvent3 =
+            MotionEvent(
+                15,
+                ACTION_MOVE,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(8f, 9f),
+                    PointerCoords(18f, 19f)
+                )
+            )
 
         mockViewGroup.returnValue = true
 
         val expected = listOf(aMove2, bMove1)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
         mockViewGroup.returnValue = false
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove2, bMove1)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bMove1, motionEvent = motionEvent3)
+            )
 
         // Assert
 
@@ -1138,29 +2281,74 @@
     }
 
     @Test
-    fun onPointerInput_2PointersMoveViewRetsTrue_everythingConsumed() {
+    fun onPointerEvent_2PointersMoveViewRetsTrue_everythingConsumed() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(11, 7.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(13f, 14f)
+                )
+            )
 
         val aMove2 = aMove1.moveBy(15.milliseconds, 8f, 9f)
         val bMove1 = bDown.moveBy(15.milliseconds, 18f, 19f)
+        val motionEvent3 =
+            MotionEvent(
+                15,
+                ACTION_MOVE,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(8f, 9f),
+                    PointerCoords(18f, 19f)
+                )
+            )
 
         mockViewGroup.returnValue = true
 
         val expected = listOf(aMove2.consumeAllChanges(), bMove1.consumeAllChanges())
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove2, bMove1)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bMove1, motionEvent = motionEvent3)
+            )
 
         // Assert
 
@@ -1172,87 +2360,234 @@
     // should be consumed).
 
     @Test
-    fun onPointerInput_downConsumedThenMove_noAdditionalConsumption() {
+    fun onPointerEvent_downConsumedThenMove_noAdditionalConsumption() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val aMove = aDownConsumed.moveTo(5.milliseconds, 6f, 7f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 7f))
+            )
         mockViewGroup.returnValue = true
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove, motionEvent = motionEvent2)
+            )
 
-        assertThat(actual).isEqualTo(aMove)
+        assertThat(actual.first()).isEqualTo(aMove)
     }
 
     @Test
-    fun onPointerInput_downConsumedThenUp_noAdditionalConsumption() {
+    fun onPointerEvent_downConsumedThenUp_noAdditionalConsumption() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         val aUp = aDownConsumed.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(
+                    PointerProperties(0)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f)
+                )
+            )
         mockViewGroup.returnValue = true
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aUp, motionEvent = motionEvent2)
+            )
 
-        assertThat(actual).isEqualTo(aUp)
+        assertThat(actual.first()).isEqualTo(aUp)
     }
 
     @Test
-    fun onPointerInput_down1ConsumedThenDown2_noAdditionalConsumption() {
+    fun onPointerEvent_down1ConsumedThenDown2_noAdditionalConsumption() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
-        val aMove1 = aDownConsumed.moveTo(5.milliseconds, 6f, 7f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val aMove1 = aDownConsumed.moveTo(5.milliseconds, 3f, 4f)
         val bDown = down(11, 5.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
+
         val expected = listOf(aMove1, bDown)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove1, bDown)
-
-        assertThat(actual).isEqualTo(expected)
-    }
-
-    @Test
-    fun onPointerInput_down1ConsumedThenDown2ThenMove_noAdditionalConsumption() {
-        val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
-        val aMove1 = aDownConsumed.moveTo(5.milliseconds, 6f, 7f)
-        val bDown = down(11, 5.milliseconds, 13f, 14f)
-        val aMove2 = aDownConsumed.moveTo(21.milliseconds, 6f, 7f)
-        val bMove = bDown.moveTo(21.milliseconds, 22f, 23f)
-        val expected = listOf(aMove2, bMove)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(
-                aMove2,
-                bMove
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
             )
 
         assertThat(actual).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_2Pointers1MoveConsumed_noAdditionalConsumption() {
+    fun onPointerEvent_down1ConsumedThenDown2ThenMove_noAdditionalConsumption() {
+        val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val aMove1 = aDownConsumed.moveTo(5.milliseconds, 3f, 4f)
+        val bDown = down(11, 5.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
+        val aMove2 = aDownConsumed.moveTo(21.milliseconds, 6f, 7f)
+        val bMove = bDown.moveTo(21.milliseconds, 22f, 23f)
+        val motionEvent3 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(6f, 7f), PointerCoords(22f, 23f))
+            )
+        val expected = listOf(aMove2, bMove)
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
+            )
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun onPointerEvent_2Pointers1MoveConsumed_noAdditionalConsumption() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(11, 7.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(13f, 14f)
+                )
+            )
 
         val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
         val bMoveConsumed = bDown.moveTo(15.milliseconds, 18f, 19f).consume(1f, 0f)
+        val motionEvent3 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                2,
+                1,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(8f, 9f),
+                    PointerCoords(18f, 19f)
+                )
+            )
 
         val expected = listOf(aMove2, bMoveConsumed)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove2, bMoveConsumed)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bMoveConsumed, motionEvent = motionEvent3)
+            )
 
         // Assert
 
@@ -1260,24 +2595,57 @@
     }
 
     @Test
-    fun onPointerInput_down1ThenDown2ConsumedThenMove_noAdditionalConsumption() {
+    fun onPointerEvent_down1ThenDown2ConsumedThenMove_noAdditionalConsumption() {
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
         val bDownConsumed = down(21, 11.milliseconds, 23f, 24f).consumeDownChange()
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aMove2 = aMove1.moveTo(31.milliseconds, 31f, 32f)
         val bMove = bDownConsumed.moveTo(31.milliseconds, 33f, 34f)
+        val motionEvent3 =
+            MotionEvent(
+                11,
+                ACTION_MOVE,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(31f, 32f), PointerCoords(33f, 34f))
+            )
 
         val expected = listOf(aMove2, bMove)
 
         // Act
-        val pointerInputHandler = pointerInteropFilter.pointerInputFilter::onPointerInput
-        pointerInputHandler.invokeOverAllPasses(aDown)
-        pointerInputHandler.invokeOverAllPasses(aMove1, bDownConsumed)
-        val actual = pointerInputHandler.invokeOverAllPasses(aMove2, bMove)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDownConsumed, motionEvent = motionEvent2)
+        )
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
+            )
 
         // Assert
         assertThat(actual).isEqualTo(expected)
@@ -1286,92 +2654,247 @@
     // Verifies resetting of consumption.
 
     @Test
-    fun onPointerInput_down1ConsumedThenUp1ThenDown2_finalDownConsumed() {
+    fun onPointerEvent_down1ConsumedThenUp1ThenDown2_finalDownConsumed() {
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val aUp = aDownConsumed.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(
+                    PointerProperties(0)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f)
+                )
+            )
         val bDown = down(11, 12.milliseconds, 13f, 14f)
+        val motionEvent3 =
+            MotionEvent(
+                12,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(13f, 14f))
+            )
         val expected = bDown.consumeAllChanges()
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, motionEvent = motionEvent2)
+        )
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bDown)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(bDown, motionEvent = motionEvent3)
+            )
 
-        assertThat(actual).isEqualTo(expected)
+        assertThat(actual.first()).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_down1ConsumedThenDown2ThenUp1ThenUp2ThenDown3_finalDownConsumed() {
+    fun onPointerEvent_down1ConsumedThenDown2ThenUp1ThenUp2ThenDown3_finalDownConsumed() {
 
         // Arrange
 
         val aDownConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDownConsumed.moveTo(22.milliseconds, 3f, 4f)
         val bDown = down(21, 22.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                22,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aUp = aMove1.up(31.milliseconds)
         val bMove1 = bDown.moveTo(31.milliseconds, 23f, 24f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val bUp = bMove1.up(41.milliseconds)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(1)),
+                arrayOf(PointerCoords(23f, 24f))
+            )
 
         val cDown = down(51, 52.milliseconds, 53f, 54f)
+        val motionEvent5 =
+            MotionEvent(
+                52,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(53f, 54f))
+            )
 
         val expected = cDown.consumeAllChanges()
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDownConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp, bMove1)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(bUp)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, bMove1, motionEvent = motionEvent3)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(bUp, motionEvent = motionEvent4)
+        )
         val actual =
-            pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(cDown)
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(cDown, motionEvent = motionEvent5)
+            )
 
         // Assert
-        assertThat(actual).isEqualTo(expected)
+        assertThat(actual.first()).isEqualTo(expected)
     }
 
     // Verification of consumption when the view rets false and then is set to return true.
 
     @Test
-    fun onPointerInput_viewRetsFalseDownThenViewRetsTrueMove_noConsumptionOfMove() {
+    fun onPointerEvent_viewRetsFalseDownThenViewRetsTrueMove_noConsumptionOfMove() {
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val aMove = aDown.moveTo(5.milliseconds, 6f, 7f)
+        val motionEvent2 =
+            MotionEvent(
+                2,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 7f))
+            )
         mockViewGroup.returnValue = false
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove, motionEvent = motionEvent2)
+            )
 
-        assertThat(actual).isEqualTo(aMove)
+        assertThat(actual.first()).isEqualTo(aMove)
     }
 
     @Test
-    fun onPointerInput_viewRetsFalseDownThenViewRetsTrueUp_noConsumptionOfUp() {
+    fun onPointerEvent_viewRetsFalseDownThenViewRetsTrueUp_noConsumptionOfUp() {
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val aUp = aDown.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
         mockViewGroup.returnValue = false
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aUp)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aUp, motionEvent = motionEvent2)
+            )
 
-        assertThat(actual).isEqualTo(aUp)
+        assertThat(actual.first()).isEqualTo(aUp)
     }
 
     @Test
-    fun onPointerInput_viewRestsFalseDown1ThenViewRetsTrueDown2_noConsumptionOfDown2() {
+    fun onPointerEvent_viewRestsFalseDown1ThenViewRetsTrueDown2_noConsumptionOfDown2() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(5.milliseconds, 6f, 7f)
+        val aMove1 = aDown.moveTo(5.milliseconds, 3f, 4f)
         val bDown = down(11, 5.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
 
         mockViewGroup.returnValue = false
 
@@ -1379,10 +2902,14 @@
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove1, bDown)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+            )
 
         // Assert
 
@@ -1390,17 +2917,44 @@
     }
 
     @Test
-    fun onPointerInput_viewRestsFalseDown1ThenViewRetsTrueDown2TheMove_noConsumptionOfMove() {
+    fun onPointerEvent_viewRestsFalseDown1ThenViewRetsTrueDown2TheMove_noConsumptionOfMove() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(5.milliseconds, 6f, 7f)
+        val aMove1 = aDown.moveTo(5.milliseconds, 3f, 4f)
         val bDown = down(11, 5.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
 
         val aMove2 = aMove1.moveTo(21.milliseconds, 22f, 23f)
         val bMove1 = bDown.moveBy(21.milliseconds, 24f, 25f)
+        val motionEvent3 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(22f, 23f), PointerCoords(24f, 25f))
+            )
 
         mockViewGroup.returnValue = false
 
@@ -1408,11 +2962,17 @@
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove2, bMove1)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bMove1, motionEvent = motionEvent3)
+            )
 
         // Assert
 
@@ -1420,17 +2980,44 @@
     }
 
     @Test
-    fun onPointerInput_viewRestsFalseDown1ThenViewRetsTrueDown2TheUp2_noConsumptionOfUp() {
+    fun onPointerEvent_viewRestsFalseDown1ThenViewRetsTrueDown2TheUp2_noConsumptionOfUp() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(5.milliseconds, 6f, 7f)
+        val aMove1 = aDown.moveTo(5.milliseconds, 3f, 4f)
         val bDown = down(11, 5.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
 
-        val aMove2 = aMove1.moveTo(21.milliseconds, 6f, 7f)
+        val aMove2 = aMove1.moveTo(21.milliseconds, 3f, 4f)
         val bUp = bDown.up(21.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                21,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
 
         mockViewGroup.returnValue = false
 
@@ -1438,11 +3025,17 @@
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(aMove2, bUp)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bUp, motionEvent = motionEvent3)
+            )
 
         // Assert
 
@@ -1450,111 +3043,286 @@
     }
 
     @Test
-    fun onPointerInput_down1ViewRetsFalseThenViewRestsTrueDown2ThenUp1ThenDown3_down3NotConsumed() {
+    fun onPointerEvent_down1ViewRetsFalseThenViewRestsTrueDown2ThenUp1ThenDown3_down3NotConsumed() {
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
-        val bDown = down(21, 22.milliseconds, 23f, 24f)
+        val bDown = down(21, 11.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                2,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aUp = aMove1.up(31.milliseconds)
         val bMove1 = bDown.moveTo(31.milliseconds, 23f, 24f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val bMove2 = bMove1.moveTo(41.milliseconds, 23f, 24f)
         val cDown = down(51, 41.milliseconds, 52f, 53f)
+        val motionEvent4 =
+            MotionEvent(
+                41,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(1), PointerProperties(0)),
+                arrayOf(PointerCoords(23f, 24f), PointerCoords(52f, 53f))
+            )
 
         mockViewGroup.returnValue = false
 
         val expected = listOf(bMove2, cDown)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aUp, bMove1)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aUp, bMove1, motionEvent = motionEvent3)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(bMove2, cDown)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(bMove2, cDown, motionEvent = motionEvent4)
+            )
 
         assertThat(actual).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_downThenMoveViewRetsFalseThenViewRetsTrueMove_moveNotConsumed() {
+    fun onPointerEvent_downThenMoveViewRetsFalseThenViewRetsTrueMove_moveNotConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val move1 = down.moveTo(5.milliseconds, 6f, 7f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 47f))
+            )
         val move2 = move1.moveTo(10.milliseconds, 11f, 12f)
+        val motionEvent3 =
+            MotionEvent(
+                10,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(11f, 12f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = false
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move1)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move1, motionEvent = motionEvent2)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(move2)
+        val actual = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move2, motionEvent = motionEvent3)
+        )
 
-        assertThat(actual).isEqualTo(move2)
+        assertThat(actual.first()).isEqualTo(move2)
     }
 
     @Test
-    fun onPointerInput_downThenMoveViewRetsFalseThenViewRetsTrueThenUp_UpNotConsumed() {
+    fun onPointerEvent_downThenMoveViewRetsFalseThenViewRetsTrueThenUp_UpNotConsumed() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val move1 = down.moveTo(5.milliseconds, 6f, 7f)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 47f))
+            )
         val up = move1.up(10.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                10,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(6f, 47f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = false
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(move1)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(move1, motionEvent = motionEvent2)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverAllPasses(up)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(up, motionEvent = motionEvent3)
+            )
 
-        assertThat(actual).isEqualTo(up)
+        assertThat(actual.first()).isEqualTo(up)
     }
 
     @Test
-    fun onPointerInput_down1ThenDown2ViewRetsFalseThenViewRetsTrueMove_moveNotConsumed() {
+    fun onPointerEvent_down1ThenDown2ViewRetsFalseThenViewRetsTrueMove_moveNotConsumed() {
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
         val bDown = down(21, 11.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_POINTER_DOWN,
+                2,
+                2,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aMove2 = aMove1.moveTo(31.milliseconds, 31f, 32f)
         val bMove = bDown.moveTo(31.milliseconds, 33f, 34f)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_MOVE,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(31f, 32f), PointerCoords(33f, 34f))
+            )
 
         val expected = listOf(aMove2, bMove)
 
         // Act
-        val pointerInputHandler = pointerInteropFilter.pointerInputFilter::onPointerInput
-        pointerInputHandler.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = false
-        pointerInputHandler.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInputHandler.invokeOverAllPasses(aMove2, bMove)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
+            )
 
         // Assert
         assertThat(actual).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_down1ThenDown2ViewRetsFalseThenViewRetsTrueUp2_moveNotConsumed() {
+    fun onPointerEvent_down1ThenDown2ViewRetsFalseThenViewRetsTrueUp2_moveNotConsumed() {
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(11.milliseconds, 3f, 4f)
         val bDown = down(21, 11.milliseconds, 23f, 24f)
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_POINTER_DOWN,
+                2,
+                2,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val aMove2 = aMove1.moveTo(31.milliseconds, 31f, 32f)
         val bUp = bDown.up(31.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                31,
+                ACTION_POINTER_UP,
+                2,
+                2,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(23f, 24f))
+            )
 
         val expected = listOf(aMove2, bUp)
 
         // Act
-        val pointerInputHandler = pointerInteropFilter.pointerInputFilter::onPointerInput
-        pointerInputHandler.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
         mockViewGroup.returnValue = false
-        pointerInputHandler.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
         mockViewGroup.returnValue = true
-        val actual = pointerInputHandler.invokeOverAllPasses(aMove2, bUp)
+        val actual =
+            pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+                pointerEventOf(aMove2, bUp, motionEvent = motionEvent3)
+            )
 
         // Assert
         assertThat(actual).isEqualTo(expected)
@@ -1563,115 +3331,20 @@
     // Verification of correct passes being used
 
     @Test
-    fun onPointerInput_1PointerDown_dispatchedDuringInitialTunnel() {
+    fun onPointerEvent_1PointerDown_dispatchedDuringInitialTunnel() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(down, PointerEventPass.InitialDown)
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-    }
-
-    @Test
-    fun onPointerInput_1PointerUp_dispatchedDuringInitialTunnel() {
-        val down = down(1, 2.milliseconds, 3f, 4f)
-        val up = down.up(5.milliseconds)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(up, PointerEventPass.InitialDown)
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-    }
-
-    @Test
-    fun onPointerInput_2PointersDown_dispatchedDuringInitialTunnel() {
-
-        // Arrange
-
-        val aDown = down(1, 2.milliseconds, 3f, 4f)
-
-        val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
-        val bDown = down(8, 7.milliseconds, 10f, 11f)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-
-        // Act
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            aMove, bDown,
-            pointerEventPass = PointerEventPass.InitialDown
-        )
-
-        // Assert
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
-    }
-
-    @Test
-    fun onPointerInput_2Pointers1Up_dispatchedDuringInitialTunnel() {
-
-        // Arrange
-
-        val aDown = down(1, 2.milliseconds, 3f, 4f)
-
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
-        val bDown = down(8, 7.milliseconds, 10f, 11f)
-
-        val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
-        val bUp = bDown.up(13.milliseconds)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
-
-        // Act
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            aMove2, bUp,
-            pointerEventPass = PointerEventPass.InitialDown
-        )
-
-        // Assert
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
-    }
-
-    @Test
-    fun onPointerInput_pointerMove_dispatchedDuringPostTunnel() {
-        val down = down(1, 2.milliseconds, 3f, 4f)
-        val move = down.moveTo(7.milliseconds, 8f, 9f)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-        mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            move,
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
-        )
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            move,
-            PointerEventPass.PostDown
-        )
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-    }
-
-    @Test
-    fun onPointerInput_downDisallowInterceptRequestedMove_moveDispatchedDuringInitialTunnel() {
-        val down = down(1, 2.milliseconds, 3f, 4f)
-        val move = down.moveTo(7.milliseconds, 8f, 9f)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-        mockViewGroup.dispatchedMotionEvents.clear()
-
-        mockViewGroup.requestDisallowInterceptTouchEvent(true)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            move,
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(down, motionEvent = motionEvent1),
             PointerEventPass.InitialDown
         )
 
@@ -1679,90 +3352,77 @@
     }
 
     @Test
-    fun onPointerInput_disallowInterceptRequestedUpDownMove_moveDispatchedDuringPostTunnel() {
-        val downA = down(1, 2.milliseconds, 3f, 4f)
-        val upA = downA.up(11.milliseconds)
-        val downB = down(21, 22.milliseconds, 23f, 24f)
-        val moveB = downB.moveTo(31.milliseconds, 32f, 33f)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(downA)
-        mockViewGroup.requestDisallowInterceptTouchEvent(true)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(upA)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(downB)
-        mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            moveB,
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
-        )
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            moveB,
-            PointerEventPass.PostDown
-        )
-
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-    }
-
-    @Test
-    fun onPointerInput_disallowInterceptTrueThenFalseThenMove_moveDispatchedDuringPostTunnel() {
+    fun onPointerEvent_1PointerUp_dispatchedDuringInitialTunnel() {
         val down = down(1, 2.milliseconds, 3f, 4f)
-        val move = down.moveTo(7.milliseconds, 8f, 9f)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-        mockViewGroup.requestDisallowInterceptTouchEvent(true)
-        mockViewGroup.requestDisallowInterceptTouchEvent(false)
-        mockViewGroup.dispatchedMotionEvents.clear()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            move,
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val up = down.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
         )
 
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            move,
-            PointerEventPass.PostDown
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(up, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown
         )
 
-        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
-    }
-
-    @Test
-    fun onPointerInput_1PointerUpConsumed_dispatchDuringInitialTunnel() {
-        val down = down(1, 2.milliseconds, 3f, 4f)
-        val upConsumed = down.up(5.milliseconds).consumeDownChange()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(upConsumed, PointerEventPass.InitialDown)
-
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
     }
 
     @Test
-    fun onPointerInput_2PointersDown2ndDownConsumed_dispatchDuringInitialTunnel() {
+    fun onPointerEvent_2PointersDown_dispatchedDuringInitialTunnel() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
-        val bDownConsumed = down(8, 7.milliseconds, 10f, 11f).consumeDownChange()
+        val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(aMove, bDownConsumed, pointerEventPass = PointerEventPass.InitialDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove, bDown, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown
+        )
 
         // Assert
 
@@ -1770,25 +3430,58 @@
     }
 
     @Test
-    fun onPointerInput_2Pointers1UpConsumed_dispatchDuringInitialTunnel() {
+    fun onPointerEvent_2Pointers1Up_dispatchedDuringInitialTunnel() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
-        val bUpConsumed = bDown.up(13.milliseconds).consumeDownChange()
+        val bUp = bDown.up(13.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                13,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(aMove2, bUpConsumed, pointerEventPass = PointerEventPass.InitialDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove2, bUp, motionEvent = motionEvent3),
+            PointerEventPass.InitialDown
+        )
 
         // Assert
 
@@ -1796,13 +3489,378 @@
     }
 
     @Test
-    fun onPointerInput_1PointerMoveConsumed_dispatchDuringPostTunnel() {
+    fun onPointerEvent_pointerMove_dispatchedDuringPostTunnel() {
         val down = down(1, 2.milliseconds, 3f, 4f)
-        val moveConsumed = down.moveTo(7.milliseconds, 8f, 9f).consume(1f, 0f)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val move = down.moveTo(7.milliseconds, 8f, 9f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(8f, 9f))
+            )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+        mockViewGroup.dispatchedMotionEvents.clear()
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(move, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown,
+            PointerEventPass.PreUp,
+            PointerEventPass.PreDown,
+            PointerEventPass.PostUp
+        )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            moveConsumed,
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(move, motionEvent = motionEvent2),
+            PointerEventPass.PostDown
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
+    }
+
+    @Test
+    fun onPointerEvent_downDisallowInterceptRequestedMove_moveDispatchedDuringInitialTunnel() {
+        val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val move = down.moveTo(7.milliseconds, 8f, 9f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(8f, 9f))
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+        mockViewGroup.dispatchedMotionEvents.clear()
+
+        mockViewGroup.requestDisallowInterceptTouchEvent(true)
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(move, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
+    }
+
+    @Test
+    fun onPointerEvent_disallowInterceptRequestedUpDownMove_moveDispatchedDuringPostTunnel() {
+        val downA = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val upA = downA.up(11.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                11,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val downB = down(21, 22.milliseconds, 23f, 24f)
+        val motionEvent3 =
+            MotionEvent(
+                21,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(23f, 24f))
+            )
+        val moveB = downB.moveTo(31.milliseconds, 32f, 33f)
+        val motionEvent4 =
+            MotionEvent(
+                31,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(32f, 33f))
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(downA, motionEvent = motionEvent1)
+        )
+        mockViewGroup.requestDisallowInterceptTouchEvent(true)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(upA, motionEvent = motionEvent2)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(downB, motionEvent = motionEvent3)
+        )
+        mockViewGroup.dispatchedMotionEvents.clear()
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(moveB, motionEvent = motionEvent4),
+            PointerEventPass.InitialDown,
+            PointerEventPass.PreUp,
+            PointerEventPass.PreDown,
+            PointerEventPass.PostUp
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(moveB, motionEvent = motionEvent4),
+            PointerEventPass.PostDown
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
+    }
+
+    @Test
+    fun onPointerEvent_disallowInterceptTrueThenFalseThenMove_moveDispatchedDuringPostTunnel() {
+        val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val move = down.moveTo(7.milliseconds, 8f, 9f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(8f, 9f))
+            )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+        mockViewGroup.requestDisallowInterceptTouchEvent(true)
+        mockViewGroup.requestDisallowInterceptTouchEvent(false)
+        mockViewGroup.dispatchedMotionEvents.clear()
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(move, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown,
+            PointerEventPass.PreUp,
+            PointerEventPass.PreDown,
+            PointerEventPass.PostUp
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(0)
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(move, motionEvent = motionEvent2),
+            PointerEventPass.PostDown
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
+    }
+
+    @Test
+    fun onPointerEvent_1PointerUpConsumed_dispatchDuringInitialTunnel() {
+        val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val upConsumed = down.up(5.milliseconds).consumeDownChange()
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(upConsumed, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown
+        )
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
+    }
+
+    @Test
+    fun onPointerEvent_2PointersDown2ndDownConsumed_dispatchDuringInitialTunnel() {
+
+        // Arrange
+
+        val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
+        val bDownConsumed = down(8, 7.milliseconds, 10f, 11f).consumeDownChange()
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_CANCEL,
+                2,
+                0,
+                arrayOf(
+                    PointerProperties(0),
+                    PointerProperties(1)
+                ),
+                arrayOf(
+                    PointerCoords(3f, 4f),
+                    PointerCoords(10f, 11f)
+                )
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+
+        // Act
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove, bDownConsumed, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown
+        )
+
+        // Assert
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
+    }
+
+    @Test
+    fun onPointerEvent_2Pointers1UpConsumed_dispatchDuringInitialTunnel() {
+
+        // Arrange
+
+        val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
+        val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
+
+        val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
+        val bUpConsumed = bDown.up(13.milliseconds).consumeDownChange()
+        val motionEvent3 =
+            MotionEvent(
+                13,
+                ACTION_CANCEL,
+                2,
+                0,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
+
+        // Act
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove2, bUpConsumed, motionEvent = motionEvent3),
+            PointerEventPass.InitialDown
+        )
+
+        // Assert
+
+        assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
+    }
+
+    @Test
+    fun onPointerEvent_1PointerMoveConsumed_dispatchDuringPostTunnel() {
+        val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val moveConsumed = down.moveTo(7.milliseconds, 8f, 9f).consume(1f, 0f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_CANCEL,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(8f, 9f))
+            )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(moveConsumed, motionEvent = motionEvent2),
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown,
@@ -1811,8 +3869,8 @@
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            moveConsumed,
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(moveConsumed, motionEvent = motionEvent2),
             PointerEventPass.PostDown
         )
 
@@ -1820,90 +3878,174 @@
     }
 
     @Test
-    fun onPointerInput_2PointersMoveConsumed_dispatchDuringPostTunnel() {
+    fun onPointerEvent_2PointersMoveConsumed_dispatchDuringPostTunnel() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(11, 7.milliseconds, 13f, 14f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
+            )
 
         val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
         val bMoveConsumed = bDown.moveTo(15.milliseconds, 18f, 19f).consume(1f, 0f)
+        val motionEvent3 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(8f, 9f), PointerCoords(18f, 19f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act 1
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(
-                listOf(aMove2, bMoveConsumed),
-                listOf(
-                    PointerEventPass.InitialDown,
-                    PointerEventPass.PreUp,
-                    PointerEventPass.PreDown,
-                    PointerEventPass.PostUp
-                )
-            )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove2, bMoveConsumed, motionEvent = motionEvent3),
+            PointerEventPass.InitialDown,
+            PointerEventPass.PreUp,
+            PointerEventPass.PreDown,
+            PointerEventPass.PostUp
+        )
 
         // Assert 1
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(2)
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(
-                aMove2, bMoveConsumed,
-                pointerEventPass = PointerEventPass.PostDown
-            )
+        // Act 2
+
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove2, bMoveConsumed, motionEvent = motionEvent3),
+            PointerEventPass.PostDown
+        )
+
+        // Assert 2
 
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(3)
     }
 
     @Test
-    fun onPointerInput_1PointerDown_consumedDuringInitialTunnel() {
+    fun onPointerEvent_1PointerDown_consumedDuringInitialTunnel() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val expected = down.consumeAllChanges()
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(down, PointerEventPass.InitialDown)
+        val actual = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(down, motionEvent = motionEvent1),
+            PointerEventPass.InitialDown
+        )
 
-        assertThat(actual).isEqualTo(expected)
+        assertThat(actual.first()).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_1PointerUp_consumedDuringInitialTunnel() {
+    fun onPointerEvent_1PointerUp_consumedDuringInitialTunnel() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val up = down.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val expected = up.consumeAllChanges()
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput
-            .invokeOverPasses(up, PointerEventPass.InitialDown)
+        val actual = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(up, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown
+        )
 
-        assertThat(actual).isEqualTo(expected)
+        assertThat(actual.first()).isEqualTo(expected)
     }
 
     @Test
-    fun onPointerInput_2PointersDown_consumedDuringInitialTunnel() {
+    fun onPointerEvent_2PointersDown_consumedDuringInitialTunnel() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
         val aMove = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         val expected = listOf(aMove, bDown).map { it.consumeAllChanges() }
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
 
         // Act
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            aMove, bDown,
-            pointerEventPass = PointerEventPass.InitialDown
+        val actual = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove, bDown, motionEvent = motionEvent2),
+            PointerEventPass.InitialDown
         )
 
         // Assert
@@ -1912,28 +4054,59 @@
     }
 
     @Test
-    fun onPointerInput_2Pointers1Up_consumedDuringInitialTunnel() {
+    fun onPointerEvent_2Pointers1Up_consumedDuringInitialTunnel() {
 
         // Arrange
 
         val aDown = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        val aMove1 = aDown.moveTo(12.milliseconds, 3f, 4f)
+        val aMove1 = aDown.moveTo(7.milliseconds, 3f, 4f)
         val bDown = down(8, 7.milliseconds, 10f, 11f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_POINTER_DOWN,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         val aMove2 = aMove1.moveTo(13.milliseconds, 3f, 4f)
         val bUp = bDown.up(13.milliseconds)
+        val motionEvent3 =
+            MotionEvent(
+                13,
+                ACTION_POINTER_UP,
+                2,
+                1,
+                arrayOf(PointerProperties(0), PointerProperties(1)),
+                arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
+            )
 
         val expected = listOf(aMove2, bUp).map { it.consumeAllChanges() }
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aDown)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(aMove1, bDown)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aDown, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
+        )
 
         // Act
 
-        val actual = pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            aMove2, bUp,
-            pointerEventPass = PointerEventPass.InitialDown
+        val actual = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(aMove2, bUp, motionEvent = motionEvent3),
+            PointerEventPass.InitialDown
         )
 
         // Assert
@@ -1942,36 +4115,67 @@
     }
 
     @Test
-    fun onPointerInput_pointerMove_consumedDuringPostTunnel() {
+    fun onPointerEvent_pointerMove_consumedDuringPostTunnel() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val move = down.moveTo(7.milliseconds, 8f, 9f)
+        val motionEvent2 =
+            MotionEvent(
+                7,
+                ACTION_MOVE,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(8f, 9f))
+            )
 
         val expected2 = move.consumeAllChanges()
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
-        val actual1 = pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            move,
+        val actual1 = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(move, motionEvent = motionEvent2),
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown,
             PointerEventPass.PostUp
         )
 
-        assertThat(actual1).isEqualTo(move)
+        assertThat(actual1.first()).isEqualTo(move)
 
-        val actual2 = pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverPasses(
-            move,
+        val actual2 = pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPasses(
+            pointerEventOf(move, motionEvent = motionEvent2),
             PointerEventPass.PostDown
         )
 
-        assertThat(actual2).isEqualTo(expected2)
+        assertThat(actual2.first()).isEqualTo(expected2)
     }
 
     @Test
     fun onCancel_cancelEventIsCorrect() {
         val down = down(1, 2.milliseconds, 3f, 4f)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
 
         pointerInteropFilter.pointerInputFilter.onCancel()
 
@@ -2002,8 +4206,20 @@
     @Test
     fun onCancel_downConsumedCancel_cancelNotDispatched() {
         val downConsumed = down(1, 2.milliseconds, 3f, 4f).consumeDownChange()
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(downConsumed)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(downConsumed, motionEvent = motionEvent1)
+        )
+
         mockViewGroup.dispatchedMotionEvents.clear()
         pointerInteropFilter.pointerInputFilter.onCancel()
 
@@ -2013,9 +4229,20 @@
     @Test
     fun onCancel_downViewRetsFalseThenCancel_cancelNotDispatched() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         mockViewGroup.returnValue = false
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
         pointerInteropFilter.pointerInputFilter.onCancel()
 
@@ -2025,10 +4252,32 @@
     @Test
     fun onCancel_downThenUpOnCancel_cancelNotDispatched() {
         val down = down(1, 2.milliseconds, 3f, 4f)
-        val up = down.up(11.milliseconds)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        val up = down.up(5.milliseconds)
+        val motionEvent2 =
+            MotionEvent(
+                5,
+                ACTION_UP,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(up)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(up, motionEvent = motionEvent2)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
         pointerInteropFilter.pointerInputFilter.onCancel()
 
@@ -2038,8 +4287,19 @@
     @Test
     fun onCancel_downThenOnCancel_cancelDispatchedOnce() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
         pointerInteropFilter.pointerInputFilter.onCancel()
 
@@ -2049,8 +4309,19 @@
     @Test
     fun onCancel_downThenOnCancelThenOnCancel_cancelDispatchedOnce() {
         val down = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
         pointerInteropFilter.pointerInputFilter.onCancel()
         pointerInteropFilter.pointerInputFilter.onCancel()
@@ -2061,15 +4332,37 @@
     @Test
     fun onCancel_downThenOnCancelThenDownThenOnCancel_cancelDispatchedTwice() {
         val down1 = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent1 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
         val down2 = down(1, 2.milliseconds, 3f, 4f)
+        val motionEvent2 =
+            MotionEvent(
+                2,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(0)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down1)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down1, motionEvent = motionEvent1)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
         pointerInteropFilter.pointerInputFilter.onCancel()
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
         mockViewGroup.dispatchedMotionEvents.clear()
 
-        pointerInteropFilter.pointerInputFilter::onPointerInput.invokeOverAllPasses(down2)
+        pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
+            pointerEventOf(down2, motionEvent = motionEvent2)
+        )
         mockViewGroup.dispatchedMotionEvents.clear()
         pointerInteropFilter.pointerInputFilter.onCancel()
         assertThat(mockViewGroup.dispatchedMotionEvents).hasSize(1)
@@ -2081,7 +4374,7 @@
     var returnValue = true
 
     override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
-        dispatchedMotionEvents.add(MotionEvent.obtain(ev))
+        dispatchedMotionEvents.add(ev!!)
         return returnValue
     }
 }
@@ -2110,8 +4403,7 @@
     eventTime.toLong(),
     action + (actionIndex shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
     numPointers,
-    // TODO(shepshapard): This is bad and temporary
-    pointerProperties.map { PointerProperties(it.id % 32) }.toTypedArray(),
+    pointerProperties,
     pointerCoords,
     0,
     0,
@@ -2123,27 +4415,42 @@
     0
 )
 
-private fun assertEquals(actual: MotionEvent, expected: MotionEvent) {
-    assertThat(actual.downTime).isEqualTo(expected.downTime)
-    assertThat(actual.eventTime).isEqualTo(expected.eventTime)
-    assertThat(actual.actionMasked).isEqualTo(expected.actionMasked)
-    assertThat(actual.actionIndex).isEqualTo(expected.actionIndex)
-    assertThat(actual.pointerCount).isEqualTo(expected.pointerCount)
+private typealias PointerEventHandler =
+            (PointerEvent, PointerEventPass, IntSize) -> List<PointerInputChange>
 
-    val actualPointerProperties = MotionEvent.PointerProperties()
-    val expectedPointerProperties = MotionEvent.PointerProperties()
-    repeat(expected.pointerCount) { index ->
-        actual.getPointerProperties(index, actualPointerProperties)
-        expected.getPointerProperties(index, expectedPointerProperties)
-        assertThat(actualPointerProperties).isEqualTo(expectedPointerProperties)
-    }
+private fun PointerEventHandler.invokeOverAllPasses(
+    pointerEvent: PointerEvent,
+    size: IntSize = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
+) = invokeOverPasses(
+    pointerEvent,
+    listOf(
+        PointerEventPass.InitialDown,
+        PointerEventPass.PreUp,
+        PointerEventPass.PreDown,
+        PointerEventPass.PostUp,
+        PointerEventPass.PostDown
+    ),
+    size = size
+)
 
-    val actualPointerCoords = MotionEvent.PointerCoords()
-    val expectedPointerCoords = MotionEvent.PointerCoords()
-    repeat(expected.pointerCount) { index ->
-        actual.getPointerCoords(index, actualPointerCoords)
-        expected.getPointerCoords(index, expectedPointerCoords)
-        assertThat(actualPointerCoords.x).isEqualTo(expectedPointerCoords.x)
-        assertThat(actualPointerCoords.y).isEqualTo(expectedPointerCoords.y)
+private fun PointerEventHandler.invokeOverPasses(
+    pointerEvent: PointerEvent,
+    vararg pointerEventPasses: PointerEventPass,
+    size: IntSize = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
+) = invokeOverPasses(pointerEvent, pointerEventPasses.toList(), size)
+
+private fun PointerEventHandler.invokeOverPasses(
+    pointerEvent: PointerEvent,
+    pointerEventPasses: List<PointerEventPass>,
+    size: IntSize = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
+): List<PointerInputChange> {
+    require(pointerEventPasses.isNotEmpty())
+    var localChanges: List<PointerInputChange> = pointerEvent.changes
+    pointerEventPasses.forEach {
+        localChanges = this.invoke(PointerEvent(localChanges, pointerEvent.motionEvent), it, size)
     }
-}
\ No newline at end of file
+    return localChanges
+}
+
+private fun pointerEventOf(vararg changes: PointerInputChange, motionEvent: MotionEvent? = null) =
+    PointerEvent(changes.toList(), motionEvent = motionEvent)
\ No newline at end of file
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropUtilsTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropUtilsTest.kt
index a22fb7a..dd9d72a 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropUtilsTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/node/PointerInteropUtilsTest.kt
@@ -19,13 +19,13 @@
 import android.view.MotionEvent
 import android.view.View
 import androidx.test.filters.SmallTest
-import androidx.ui.core.PointerInputChange
+import androidx.ui.core.PointerEvent
+import androidx.ui.geometry.Offset
 import androidx.ui.testutils.down
 import androidx.ui.testutils.moveTo
 import androidx.ui.testutils.up
-import androidx.ui.unit.IntOffset
 import androidx.ui.unit.milliseconds
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -34,15 +34,14 @@
 @RunWith(JUnit4::class)
 class PointerInteropUtilsTest {
 
-    @Test(expected = IllegalStateException::class)
-    fun toMotionEventScope_emptyList_throws() {
-        val list = listOf<PointerInputChange>()
-        list.toMotionEventScope(IntOffset.Origin) {}
+    @Test(expected = IllegalArgumentException::class)
+    fun toMotionEventScope_noPlatformEvent_throws() {
+        val pointerEvent = PointerEvent(listOf(), motionEvent = null)
+        pointerEvent.toMotionEventScope(Offset.Zero) {}
     }
 
     @Test
     fun toMotionEventScope_1stPointerDownEvent_motionEventIsCorrect() {
-        val list = listOf(down(1, 2.milliseconds, 3f, 4f))
         val expected =
             MotionEvent(
                 2,
@@ -52,18 +51,18 @@
                 arrayOf(PointerProperties(1)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(down(1, 2.milliseconds, 3f, 4f)), expected)
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_1stPointerUpEvent_motionEventIsCorrect() {
-        val list = listOf(down(1, 2.milliseconds, 3f, 4f).up(5.milliseconds))
         val expected =
             MotionEvent(
                 5,
@@ -73,21 +72,23 @@
                 arrayOf(PointerProperties(1)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(
+            listOf(down(1, 2.milliseconds, 3f, 4f).up(5.milliseconds)),
+            expected
+        )
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_2ndPointerDownEventAs1stPointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 3f, 4f)
         val pointer2 = down(8, 7.milliseconds, 10f, 11f)
-
-        val list = listOf(pointer1, pointer2)
         val expected =
             MotionEvent(
                 7,
@@ -97,21 +98,20 @@
                 arrayOf(PointerProperties(1), PointerProperties(8)),
                 arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1, pointer2), expected)
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_2ndPointerDownEventAs2ndPointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 3f, 4f)
         val pointer2 = down(8, 7.milliseconds, 10f, 11f)
-
-        val list = listOf(pointer2, pointer1)
         val expected =
             MotionEvent(
                 7,
@@ -121,21 +121,20 @@
                 arrayOf(PointerProperties(8), PointerProperties(1)),
                 arrayOf(PointerCoords(10f, 11f), PointerCoords(3f, 4f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer2, pointer1), expected)
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_2ndPointerUpEventAs1stPointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 3f, 4f)
         val pointer2 = down(8, 2.milliseconds, 10f, 11f).up(7.milliseconds)
-
-        val list = listOf(pointer1, pointer2)
         val expected =
             MotionEvent(
                 7,
@@ -145,21 +144,20 @@
                 arrayOf(PointerProperties(1), PointerProperties(8)),
                 arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1, pointer2), expected)
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_2ndPointerUpEventAs2ndPointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 3f, 4f)
         val pointer2 = down(8, 2.milliseconds, 10f, 11f).up(7.milliseconds)
-
-        val list = listOf(pointer2, pointer1)
         val expected =
             MotionEvent(
                 7,
@@ -169,20 +167,19 @@
                 arrayOf(PointerProperties(8), PointerProperties(1)),
                 arrayOf(PointerCoords(10f, 11f), PointerCoords(3f, 4f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer2, pointer1), expected)
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_moveEvent1Pointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 8f, 9f)
-
-        val list = listOf(pointer1)
         val expected =
             MotionEvent(
                 7,
@@ -192,21 +189,20 @@
                 arrayOf(PointerProperties(1)),
                 arrayOf(PointerCoords(8f, 9f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1), expected)
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_moveEvent2Pointers_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 8f, 9f)
         val pointer2 = down(11, 12.milliseconds, 13f, 14f).moveTo(17.milliseconds, 18f, 19f)
-
-        val list = listOf(pointer1, pointer2)
         val expected =
             MotionEvent(
                 7,
@@ -216,20 +212,19 @@
                 arrayOf(PointerProperties(1), PointerProperties(11)),
                 arrayOf(PointerCoords(8f, 9f), PointerCoords(18f, 19f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1, pointer2), expected)
 
-        list.toMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_globalOffsetsSet1Pointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f)
-
-        val list = listOf(pointer1)
         val expected =
             MotionEvent(
                 2,
@@ -239,21 +234,20 @@
                 arrayOf(PointerProperties(1)),
                 arrayOf(PointerCoords(13f, 104f))
             ).apply { offsetLocation(-10f, -100f) }
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1), expected)
 
-        list.toMotionEventScope(IntOffset(10, 100)) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset(10f, 100f)) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toMotionEventScope_globalOffsetsSet2Pointers_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 3f, 4f)
         val pointer2 = down(8, 7.milliseconds, 10f, 11f)
-
-        val list = listOf(pointer2, pointer1)
         val expected =
             MotionEvent(
                 7,
@@ -263,26 +257,25 @@
                 arrayOf(PointerProperties(8), PointerProperties(1)),
                 arrayOf(PointerCoords(110f, 1011f), PointerCoords(103f, 1004f))
             ).apply { offsetLocation(-100f, -1000f) }
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer2, pointer1), expected)
 
-        list.toMotionEventScope(IntOffset(100, 1000)) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toMotionEventScope(Offset(100f, 1000f)) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
-    @Test(expected = IllegalStateException::class)
-    fun toCancelMotionEventScope_emptyList_throws() {
-        val list = listOf<PointerInputChange>()
-        list.toCancelMotionEventScope(IntOffset.Origin) {}
+    @Test(expected = IllegalArgumentException::class)
+    fun toCancelMotionEventScope_noPlatformEvent_throws() {
+        val pointerEvent = PointerEvent(listOf(), motionEvent = null)
+        pointerEvent.toCancelMotionEventScope(Offset.Zero) {}
     }
 
     @Test
     fun toCancelMotionEventScope_1Pointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 8f, 9f)
-
-        val list = listOf(pointer1)
         val expected =
             MotionEvent(
                 7,
@@ -292,21 +285,20 @@
                 arrayOf(PointerProperties(1)),
                 arrayOf(PointerCoords(8f, 9f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1), expected)
 
-        list.toCancelMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toCancelMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toCancelMotionEventScope_2Pointers_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 8f, 9f)
         val pointer2 = down(11, 12.milliseconds, 13f, 14f).moveTo(17.milliseconds, 18f, 19f)
-
-        val list = listOf(pointer1, pointer2)
         val expected =
             MotionEvent(
                 7,
@@ -316,21 +308,20 @@
                 arrayOf(PointerProperties(1), PointerProperties(11)),
                 arrayOf(PointerCoords(8f, 9f), PointerCoords(18f, 19f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1, pointer2), expected)
 
-        list.toCancelMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toCancelMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toCancelMotionEventScope_2PointersAltOrder_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 8f, 9f)
         val pointer2 = down(11, 12.milliseconds, 13f, 14f).moveTo(7.milliseconds, 18f, 19f)
-
-        val list = listOf(pointer2, pointer1)
         val expected =
             MotionEvent(
                 7,
@@ -340,20 +331,19 @@
                 arrayOf(PointerProperties(11), PointerProperties(1)),
                 arrayOf(PointerCoords(18f, 19f), PointerCoords(8f, 9f))
             )
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer2, pointer1), expected)
 
-        list.toCancelMotionEventScope(IntOffset.Origin) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toCancelMotionEventScope(Offset.Zero) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toCancelMotionEventScope_globalOffsetsSet1Pointer_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f)
-
-        val list = listOf(pointer1)
         val expected =
             MotionEvent(
                 2,
@@ -363,21 +353,20 @@
                 arrayOf(PointerProperties(1)),
                 arrayOf(PointerCoords(13f, 104f))
             ).apply { offsetLocation(-10f, -100f) }
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer1), expected)
 
-        list.toCancelMotionEventScope(IntOffset(10, 100)) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toCancelMotionEventScope(Offset(10f, 100f)) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
     fun toCancelMotionEventScope_globalOffsetsSet2Pointers_motionEventIsCorrect() {
         val pointer1 = down(1, 2.milliseconds, 3f, 4f).moveTo(7.milliseconds, 3f, 4f)
         val pointer2 = down(8, 7.milliseconds, 10f, 11f)
-
-        val list = listOf(pointer2, pointer1)
         val expected =
             MotionEvent(
                 7,
@@ -387,13 +376,14 @@
                 arrayOf(PointerProperties(8), PointerProperties(1)),
                 arrayOf(PointerCoords(110f, 1011f), PointerCoords(103f, 1004f))
             ).apply { offsetLocation(-100f, -1000f) }
-        lateinit var actual: MotionEvent
+        val pointerEvent = PointerEvent(listOf(pointer2, pointer1), expected)
 
-        list.toCancelMotionEventScope(IntOffset(100, 1000)) {
+        lateinit var actual: MotionEvent
+        pointerEvent.toCancelMotionEventScope(Offset(100f, 1000f)) {
             actual = it
         }
 
-        assertEquals(actual, expected)
+        assertThat(actual).isSameInstanceAs(expected)
     }
 
     @Test
@@ -456,19 +446,16 @@
 )
 
 private fun assertEquals(actual: MotionEvent, expected: MotionEvent) {
-    Truth.assertThat(actual.downTime).isEqualTo(expected.downTime)
-    Truth.assertThat(actual.eventTime).isEqualTo(expected.eventTime)
-    Truth.assertThat(actual.actionMasked).isEqualTo(expected.actionMasked)
-    Truth.assertThat(actual.actionIndex).isEqualTo(expected.actionIndex)
-    Truth.assertThat(actual.pointerCount).isEqualTo(expected.pointerCount)
+    assertThat(actual.downTime).isEqualTo(expected.downTime)
+    assertThat(actual.eventTime).isEqualTo(expected.eventTime)
+    assertThat(actual.actionMasked).isEqualTo(expected.actionMasked)
+    assertThat(actual.actionIndex).isEqualTo(expected.actionIndex)
+    assertThat(actual.pointerCount).isEqualTo(expected.pointerCount)
 
-    val actualPointerProperties = MotionEvent.PointerProperties()
-    val expectedPointerProperties = MotionEvent.PointerProperties()
-    repeat(expected.pointerCount) { index ->
-        actual.getPointerProperties(index, actualPointerProperties)
-        expected.getPointerProperties(index, expectedPointerProperties)
-        Truth.assertThat(actualPointerProperties).isEqualTo(expectedPointerProperties)
-    }
+    assertEqualToolTypes(actual, expected)
+
+    // Equal pointer properties
+    assertEqualPointerProperties(actual, expected)
 
     // Equal pointer coords relative to local region.
     assertEqualPointerCoords(actual, expected)
@@ -480,6 +467,22 @@
     )
 }
 
+private fun assertEqualToolTypes(actual: MotionEvent, expected: MotionEvent) {
+    repeat(expected.pointerCount) { index ->
+        assertThat(actual.getToolType(index)).isEqualTo(expected.getToolType(index))
+    }
+}
+
+private fun assertEqualPointerProperties(actual: MotionEvent, expected: MotionEvent) {
+    val actualPointerProperties = MotionEvent.PointerProperties()
+    val expectedPointerProperties = MotionEvent.PointerProperties()
+    repeat(expected.pointerCount) { index ->
+        actual.getPointerProperties(index, actualPointerProperties)
+        expected.getPointerProperties(index, expectedPointerProperties)
+        assertThat(actualPointerProperties).isEqualTo(expectedPointerProperties)
+    }
+}
+
 /**
  * Asserts that 2 [MotionEvent]s' [PointerCoords] are the same.
  */
@@ -489,8 +492,8 @@
     repeat(expected.pointerCount) { index ->
         actual.getPointerCoords(index, actualPointerCoords)
         expected.getPointerCoords(index, expectedPointerCoords)
-        Truth.assertThat(actualPointerCoords.x).isEqualTo(expectedPointerCoords.x)
-        Truth.assertThat(actualPointerCoords.y).isEqualTo(expectedPointerCoords.y)
+        assertThat(actualPointerCoords.x).isEqualTo(expectedPointerCoords.x)
+        assertThat(actualPointerCoords.y).isEqualTo(expectedPointerCoords.y)
     }
 }
 
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/PointerInput.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/PointerInput.kt
new file mode 100644
index 0000000..86bc03b
--- /dev/null
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/PointerInput.kt
@@ -0,0 +1,51 @@
+/*
+ * 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
+
+import android.view.MotionEvent
+import androidx.ui.core.pointerinput.PointerInputEvent
+
+internal actual class InternalPointerEvent constructor(
+    actual var changes: MutableMap<PointerId, PointerInputChange>,
+    val motionEvent: MotionEvent
+) {
+    actual constructor(
+        changes: MutableMap<PointerId, PointerInputChange>,
+        pointerInputEvent: PointerInputEvent
+    ) : this(changes, pointerInputEvent.motionEvent)
+}
+
+/**
+ * Describes a pointer input change event that has occurred at a particular point in time.
+ */
+actual class PointerEvent internal constructor(
+    /**
+     * The changes.
+     */
+    actual val changes: List<PointerInputChange>,
+    internal val motionEvent: MotionEvent?
+) {
+    internal actual constructor(
+        changes: List<PointerInputChange>,
+        internalPointerEvent: InternalPointerEvent?
+    ) : this(changes, internalPointerEvent?.motionEvent)
+
+    /**
+     * @param changes The changes.
+     */
+    actual constructor(changes: List<PointerInputChange>) : this(changes, motionEvent = null)
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/pointerinput/MotionEventAdapter.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/pointerinput/MotionEventAdapter.kt
index 84aa612..81a376c 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/pointerinput/MotionEventAdapter.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/pointerinput/MotionEventAdapter.kt
@@ -36,8 +36,12 @@
 
     private var nextId = 0L
 
+    /**
+     * Whenever a new MotionEvent pointer is added, we create a new PointerId that is associated
+     * with it. This holds that association.
+     */
     @VisibleForTesting
-    internal val intIdToPointerIdMap: MutableMap<Int, PointerId> = mutableMapOf()
+    internal val motionEventToComposePointerIdMap: MutableMap<Int, PointerId> = mutableMapOf()
 
     /**
      * Converts a single [MotionEvent] from an Android event stream into a [PointerInputEvent], or
@@ -53,7 +57,7 @@
     internal fun convertToPointerInputEvent(motionEvent: MotionEvent): PointerInputEvent? {
 
         if (motionEvent.actionMasked == ACTION_CANCEL) {
-            intIdToPointerIdMap.clear()
+            motionEventToComposePointerIdMap.clear()
             return null
         }
 
@@ -69,8 +73,11 @@
             else -> null
         }
 
+        // TODO(shepshapard): Avoid allocating for every event.
         val pointers: MutableList<PointerInputEventData> = mutableListOf()
 
+        // This converts the MotionEvent into a list of PointerInputEventData, and updates
+        // internal record keeping.
         @Suppress("NAME_SHADOWING")
         motionEvent.asOffsetToScreen { motionEvent ->
             for (i in 0 until motionEvent.pointerCount) {
@@ -80,20 +87,11 @@
 
         return PointerInputEvent(
             Uptime(motionEvent.eventTime * NanosecondsPerMillisecond),
-            pointers
+            pointers,
+            motionEvent
         )
     }
 
-    private inline fun MotionEvent.asOffsetToScreen(block: (MotionEvent) -> Unit) {
-        // Mutate the motion event to be relative to the screen. This is required to create a
-        // valid PointerInputEvent.
-        val offsetX = rawX - x
-        val offsetY = rawY - y
-        offsetLocation(offsetX, offsetY)
-        block(this)
-        offsetLocation(-offsetX, -offsetY)
-    }
-
     /**
      * Creates a new PointerInputEventData.
      */
@@ -104,18 +102,18 @@
         upIndex: Int?
     ): PointerInputEventData {
 
-        val pointerIdInt = motionEvent.getPointerId(index)
+        val motionEventPointerId = motionEvent.getPointerId(index)
 
         val pointerId =
             when (index) {
                 downIndex ->
                     PointerId(nextId++).also {
-                        intIdToPointerIdMap[pointerIdInt] = it
+                        motionEventToComposePointerIdMap[motionEventPointerId] = it
                     }
                 upIndex ->
-                    intIdToPointerIdMap.remove(pointerIdInt)
+                    motionEventToComposePointerIdMap.remove(motionEventPointerId)
                 else ->
-                    intIdToPointerIdMap[pointerIdInt]
+                    motionEventToComposePointerIdMap[motionEventPointerId]
             } ?: throw IllegalStateException(
                 "Compose assumes that all pointer ids in MotionEvents are first provided " +
                         "alongside ACTION_DOWN or ACTION_POINTER_DOWN.  This appears not " +
@@ -132,24 +130,37 @@
             )
         )
     }
+}
 
-    /**
-     * Creates a new PointerInputData.
-     */
-    private fun createPointerInputData(
-        timestamp: Uptime,
-        motionEvent: MotionEvent,
-        index: Int,
-        upIndex: Int?
-    ): PointerInputData {
-        val pointerCoords = MotionEvent.PointerCoords()
-        motionEvent.getPointerCoords(index, pointerCoords)
-        val offset = Offset(pointerCoords.x, pointerCoords.y)
+/**
+ * Creates a new PointerInputData.
+ */
+private fun createPointerInputData(
+    timestamp: Uptime,
+    motionEvent: MotionEvent,
+    index: Int,
+    upIndex: Int?
+): PointerInputData {
+    val pointerCoords = MotionEvent.PointerCoords()
+    motionEvent.getPointerCoords(index, pointerCoords)
+    val offset = Offset(pointerCoords.x, pointerCoords.y)
 
-        return PointerInputData(
-            timestamp,
-            offset,
-            index != upIndex
-        )
-    }
+    return PointerInputData(
+        timestamp,
+        offset,
+        index != upIndex
+    )
+}
+
+/**
+ * Mutates the MotionEvent to be relative to the screen.
+ *
+ * This is required to create a valid PointerInputEvent.
+ */
+private inline fun MotionEvent.asOffsetToScreen(block: (MotionEvent) -> Unit) {
+    val offsetX = rawX - x
+    val offsetY = rawY - y
+    offsetLocation(offsetX, offsetY)
+    block(this)
+    offsetLocation(-offsetX, -offsetY)
 }
\ No newline at end of file
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/pointerinput/PointerInputEvent.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/pointerinput/PointerInputEvent.kt
new file mode 100644
index 0000000..c7c95cb
--- /dev/null
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/core/pointerinput/PointerInputEvent.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.pointerinput
+
+import android.view.MotionEvent
+import androidx.ui.unit.Uptime
+
+internal actual class PointerInputEvent(
+    actual val uptime: Uptime,
+    actual val pointers: List<PointerInputEventData>,
+    val motionEvent: MotionEvent
+)
\ No newline at end of file
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropFilter.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropFilter.kt
index 72fc578..616aa31 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropFilter.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropFilter.kt
@@ -18,6 +18,7 @@
 
 import android.os.SystemClock
 import androidx.ui.core.Modifier
+import androidx.ui.core.PointerEvent
 import androidx.ui.core.PointerEventPass
 import androidx.ui.core.PointerInputChange
 import androidx.ui.core.anyChangeConsumed
@@ -26,7 +27,7 @@
 import androidx.ui.core.consumeAllChanges
 import androidx.ui.core.pointerinput.PointerInputFilter
 import androidx.ui.core.pointerinput.PointerInputModifier
-import androidx.ui.unit.IntOffset
+import androidx.ui.geometry.Offset
 import androidx.ui.unit.IntSize
 import androidx.ui.unit.milliseconds
 import androidx.ui.util.fastAny
@@ -79,9 +80,6 @@
     val view: AndroidViewHolder
 ) : PointerInputModifier {
 
-    // Reusable to avoid extra allocations.
-    private val reusableLocationInWindow by lazy { IntArray(2) }
-
     /**
      * The 3 possible states
      */
@@ -122,8 +120,18 @@
                 pass: PointerEventPass,
                 bounds: IntSize
             ): List<PointerInputChange> {
+                // No implementation as onPointerEvent is overridden.
+                // The super method will eventually be removed so this is just temporary.
+                throw NotImplementedError("This method is temporary and should never be called")
+            }
+
+            override fun onPointerEvent(
+                pointerEvent: PointerEvent,
+                pass: PointerEventPass,
+                bounds: IntSize
+            ): List<PointerInputChange> {
                 @Suppress("NAME_SHADOWING")
-                var changes = changes
+                var changes = pointerEvent.changes
 
                 // If we were told to disallow intercept, or if the event was a down or up event,
                 // we dispatch to Android as early as possible.  If the event is a move event and
@@ -136,10 +144,10 @@
 
                 if (state !== DispatchToViewState.NotDispatching) {
                     if (pass == PointerEventPass.InitialDown && dispatchDuringInitialTunnel) {
-                        changes = dispatchToView(changes)
+                        changes = dispatchToView(pointerEvent)
                     }
                     if (pass == PointerEventPass.PostDown && !dispatchDuringInitialTunnel) {
-                        changes = dispatchToView(changes)
+                        changes = dispatchToView(pointerEvent)
                     }
                 }
                 if (pass == PointerEventPass.PostDown) {
@@ -176,42 +184,33 @@
             /**
              * Dispatches to the Android View.
              *
-             * Also consumes aspects of [changes] and updates our [state] accordingly.
+             * Also consumes aspects of [pointerEvent] and updates our [state] accordingly.
              *
-             * Will dispatch ACTION_CANCEL if any aspect of [changes] has been consumed and
+             * Will dispatch ACTION_CANCEL if any aspect of [pointerEvent] has been consumed and
              * update our [state] accordingly.
              *
-             * @param changes The changes to dispatch.
+             * @param pointerEvent The change to dispatch.
              * @return The resulting changes (fully consumed or untouched).
              */
-            private fun dispatchToView(changes: List<PointerInputChange>):
+            private fun dispatchToView(pointerEvent: PointerEvent):
                     List<PointerInputChange> {
 
-                @Suppress("NAME_SHADOWING")
-                var changes = changes
-
-                // TODO(b/158034713): This should likely not be the view's location in window, but
-                //  instead should take the global position of this PointerInteropFilter.
-                //  Depending on future work, this may or may not be the case.
-                val globalToLocalOffset =
-                    reusableLocationInWindow.run {
-                        view.getLocationInWindow(this)
-                        IntOffset(this[0], this[1])
-                    }
+                var changes = pointerEvent.changes
 
                 if (changes.fastAny { it.anyChangeConsumed() }) {
                     // We should no longer dispatch to the Android View.
                     if (state === DispatchToViewState.Dispatching) {
-                        // If we were dipatching, send ACTION_CANCEL.
-                        changes.toCancelMotionEventScope(globalToLocalOffset) { motionEvent ->
+                        // If we were dispatching, send ACTION_CANCEL.
+                        pointerEvent.toCancelMotionEventScope(
+                            Offset(view.x, view.y)
+                        ) { motionEvent ->
                             view.dispatchTouchEvent(motionEvent)
                         }
                     }
                     state = DispatchToViewState.NotDispatching
                 } else {
                     // Dispatch and update our state with the result.
-
-                    changes.toMotionEventScope(globalToLocalOffset) { motionEvent ->
+                    pointerEvent.toMotionEventScope(Offset(view.x, view.y)) { motionEvent ->
                         state = if (view.dispatchTouchEvent(motionEvent)) {
                             DispatchToViewState.Dispatching
                         } else {
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropUtils.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropUtils.kt
index baa8a1c..a71b846 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropUtils.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/node/PointerInteropUtils.kt
@@ -19,47 +19,37 @@
 import android.os.SystemClock
 import android.view.InputDevice
 import android.view.MotionEvent
-import androidx.ui.core.PointerInputChange
-import androidx.ui.core.changedToDownIgnoreConsumed
-import androidx.ui.core.changedToUpIgnoreConsumed
+import android.view.MotionEvent.ACTION_CANCEL
+import androidx.ui.core.PointerEvent
+import androidx.ui.geometry.Offset
 import androidx.ui.unit.Duration
-import androidx.ui.unit.IntOffset
 import androidx.ui.unit.NanosecondsPerMillisecond
 import androidx.ui.unit.milliseconds
 
-// TODO(shepshapard): Refactor in order to remove the need for current.uptime!!
 /**
- * Converts to a [MotionEvent], runs [block] with it, and recycles the [MotionEvent].
+ * Converts to a [MotionEvent] and runs [block] with it.
  *
- * @param regionToGlobalOffset The global offset of the region where this [MotionEvent] is being
- * used.  This will be added to each [PointerInputChange]'s position in order set raw coordinates
- * correctly, and will be subtracted via [MotionEvent.offsetLocation] so the [MotionEvent]
- * is still offset to be relative to the region.
- * @param block The block to be executed with the created [MotionEvent].
+ * @param offset The offset to be applied to the resulting [MotionEvent].
+ * @param block The block to be executed with the resulting [MotionEvent].
  */
-internal fun List<PointerInputChange>.toMotionEventScope(
-    regionToGlobalOffset: IntOffset,
+internal fun PointerEvent.toMotionEventScope(
+    offset: Offset,
     block: (MotionEvent) -> Unit
 ) {
-    toMotionEventScope(regionToGlobalOffset, block, false)
+    toMotionEventScope(offset, block, false)
 }
 
 /**
- * Converts to an [MotionEvent.ACTION_CANCEL] [MotionEvent], runs [block] with it, and recycles the
- * [MotionEvent].
+ * Converts to an [MotionEvent.ACTION_CANCEL] [MotionEvent] and runs [block] with it.
  *
- * @param regionToGlobalOffset The global offset of the region where this [MotionEvent] is being
- * used.  This will be added to each [PointerInputChange]'s position in order set raw coordinates
- * correctly, and will be subtracted via [MotionEvent.offsetLocation] so the [MotionEvent]
- * is still offset to be relative to the region.
- * @param block The block to be executed with the created [MotionEvent].
+ * @param offset The offset to be applied to the resulting [MotionEvent].
+ * @param block The block to be executed with the resulting [MotionEvent].
  */
-// TODO(shepshapard): Refactor in order to remove the need for current.uptime!!
-internal fun List<PointerInputChange>.toCancelMotionEventScope(
-    regionToGlobalOffset: IntOffset,
+internal fun PointerEvent.toCancelMotionEventScope(
+    offset: Offset,
     block: (MotionEvent) -> Unit
 ) {
-    toMotionEventScope(regionToGlobalOffset, block, true)
+    toMotionEventScope(offset, block, true)
 }
 
 internal fun emptyCancelMotionEventScope(
@@ -69,111 +59,33 @@
     // Does what ViewGroup does when it needs to send a minimal ACTION_CANCEL event.
     val nowMillis = now.nanoseconds / NanosecondsPerMillisecond
     val motionEvent =
-        MotionEvent.obtain(nowMillis, nowMillis, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
-    motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+        MotionEvent.obtain(nowMillis, nowMillis, ACTION_CANCEL, 0.0f, 0.0f, 0)
+    motionEvent.source = InputDevice.SOURCE_UNKNOWN
     block(motionEvent)
     motionEvent.recycle()
 }
 
-private fun List<PointerInputChange>.toMotionEventScope(
-    regionToGlobalOffset: IntOffset,
+private fun PointerEvent.toMotionEventScope(
+    offset: Offset,
     block: (MotionEvent) -> Unit,
     cancel: Boolean
 ) {
-    // We need to make sure this is not empty
-    check(isNotEmpty())
+    requireNotNull(motionEvent) {
+        "The PointerEvent receiver cannot have a null MotionEvent."
+    }
 
-    // We derive the values of each aspect of MotionEvent...
-
-    val eventTime =
-        first().current.uptime!!.nanoseconds / NanosecondsPerMillisecond
-
-    val action = if (cancel) {
-        MotionEvent.ACTION_CANCEL
-    } else {
-        if (all { it.changedToDownIgnoreConsumed() }) {
-            createAction(MotionEvent.ACTION_DOWN, 0)
-        } else if (all { it.changedToUpIgnoreConsumed() }) {
-            createAction(MotionEvent.ACTION_UP, 0)
-        } else {
-            val downIndex = indexOfFirst { it.changedToDownIgnoreConsumed() }
-            if (downIndex != -1) {
-                createAction(MotionEvent.ACTION_POINTER_DOWN, downIndex)
-            } else {
-                val upIndex = indexOfFirst { it.changedToUpIgnoreConsumed() }
-                if (upIndex != -1) {
-                    createAction(MotionEvent.ACTION_POINTER_UP, upIndex)
-                } else {
-                    createAction(MotionEvent.ACTION_MOVE, 0)
-                }
-            }
+    motionEvent.apply {
+        val oldAction = action
+        if (cancel) {
+            action = ACTION_CANCEL
         }
-    }
 
-    val numPointers = size
+        offsetLocation(-offset.x, -offset.y)
 
-    // TODO(b/154136736): "(it.id.value % 32).toInt()" is very fishy, but android never expects
-    //  the id to be larger than 31.
-    val pointerProperties =
-        map {
-            MotionEvent.PointerProperties().apply {
-                id = (it.id.value % 32).toInt()
-                toolType = MotionEvent.TOOL_TYPE_UNKNOWN
-            }
-        }.toTypedArray()
-
-    val pointerCoords =
-        map {
-            val offsetX =
-                if (it.changedToUpIgnoreConsumed()) {
-                    it.previous.position!!.x
-                } else {
-                    it.current.position!!.x
-                }
-            val offsetY =
-                if (it.changedToUpIgnoreConsumed()) {
-                    it.previous.position!!.y
-                } else {
-                    it.current.position!!.y
-                }
-            // We add the regionToGlobalOffset so that the raw coordinates are correct.
-            val rawX = offsetX + regionToGlobalOffset.x
-            val rawY = offsetY + regionToGlobalOffset.y
-
-            MotionEvent.PointerCoords().apply {
-                this.x = rawX
-                this.y = rawY
-            }
-        }.toTypedArray()
-
-    // ... Then we create the MotionEvent, dispatch it to block, and recycle it.
-
-    // TODO(b/154136736): Downtime as 0 isn't right.  Not sure it matters.
-    MotionEvent.obtain(
-        0,
-        eventTime,
-        action,
-        numPointers,
-        pointerProperties,
-        pointerCoords,
-        0,
-        0,
-        0f,
-        0f,
-        0,
-        0,
-        0,
-        0
-    ).apply {
-        // We subtract the regionToGlobalOffset so the local coordinates are correct.
-        offsetLocation(
-            -regionToGlobalOffset.x.toFloat(),
-            -regionToGlobalOffset.y.toFloat()
-        )
         block(this)
-        recycle()
-    }
-}
 
-private fun createAction(actionType: Int, actionIndex: Int) =
-    actionType + (actionIndex shl MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+        offsetLocation(offset.x, offset.y)
+
+        action = oldAction
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/PointerInput.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/PointerInput.kt
index 87b77d4..25d1a58 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/PointerInput.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/PointerInput.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.Immutable
 import androidx.compose.Stable
+import androidx.ui.core.pointerinput.PointerInputEvent
 import androidx.ui.geometry.Offset
 import androidx.ui.unit.IntSize
 import androidx.ui.unit.Uptime
@@ -29,20 +30,30 @@
  * it is efficient to split the changes between those that are relevant to the sub tree and those
  * that are not.
  */
-internal data class InternalPointerEvent(
+internal expect class InternalPointerEvent(
+    changes: MutableMap<PointerId, PointerInputChange>,
+    pointerInputEvent: PointerInputEvent
+) {
     var changes: MutableMap<PointerId, PointerInputChange>
-)
+}
 
 /**
  * Describes a pointer input change event that has occurred at a particular point in time.
- *
- * Right now this just contains a list of [PointerInputChange]s but as refactoring continues,
- * will contain more data that is global to the change, such as the current [Uptime] and the
- * [Uptime] of the previous [PointerEvent].
  */
-data class PointerEvent(
+expect class PointerEvent internal constructor(
+    changes: List<PointerInputChange>,
+    internalPointerEvent: InternalPointerEvent?
+) {
+    /**
+     * @param changes The changes.
+     */
+    constructor(changes: List<PointerInputChange>)
+
+    /**
+     * The changes.
+     */
     val changes: List<PointerInputChange>
-)
+}
 
 /**
  * Describes a change that has occurred for a particular pointer, as well as how much of the change
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/HitPathTracker.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/HitPathTracker.kt
index 380629e..02cdc4b 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/HitPathTracker.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/HitPathTracker.kt
@@ -19,10 +19,10 @@
 import androidx.ui.core.CustomEvent
 import androidx.ui.core.CustomEventDispatcher
 import androidx.ui.core.InternalPointerEvent
+import androidx.ui.core.PointerEvent
 import androidx.ui.core.PointerEventPass
 import androidx.ui.core.PointerId
 import androidx.ui.core.PointerInputChange
-import androidx.ui.core.PointerEvent
 import androidx.ui.unit.IntOffset
 import androidx.ui.unit.IntSize
 import androidx.ui.unit.plus
@@ -539,7 +539,8 @@
         pass: PointerEventPass,
         size: IntSize
     ) {
-        filter.onPointerEvent(toPointerEvent(), pass, size).forEach {
+        val pointerEvent = PointerEvent(this.changes.values.toList(), this)
+        filter.onPointerEvent(pointerEvent, pass, size).forEach {
             this.changes[it.id] = it
         }
     }
@@ -562,8 +563,6 @@
         addOffset(-position)
     }
 
-    private fun InternalPointerEvent.toPointerEvent() = PointerEvent(changes.values.toList())
-
     private inline fun <K, V> MutableMap<K, V>.replaceEverything(f: (V) -> V) {
         for (entry in this) {
             entry.setValue(f(entry.value))
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEvent.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEvent.kt
index 3ac46db..e72a8e0 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEvent.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEvent.kt
@@ -26,17 +26,17 @@
  *
  * All pointer locations are relative to the device screen.
  */
-internal data class PointerInputEvent(
-    val uptime: Uptime,
+internal expect class PointerInputEvent {
+    val uptime: Uptime
     val pointers: List<PointerInputEventData>
-)
+}
 
 /**
  * Data that describes a particular pointer
  *
  * All pointer locations are relative to the device screen.
  */
-data class PointerInputEventData(
+internal data class PointerInputEventData(
     val id: PointerId,
     val pointerInputData: PointerInputData
 )
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessor.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessor.kt
index 9f0c333..1ec35d2 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessor.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/pointerinput/PointerInputEventProcessor.kt
@@ -132,7 +132,7 @@
                 previousPointerInputData.remove(it.id)
             }
         }
-        return InternalPointerEvent(changes)
+        return InternalPointerEvent(changes, pointerInputEvent)
     }
 
     /**