Merge "Messager implementation for KSP" into androidx-master-dev
diff --git a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
index 4321a86..a830e81 100644
--- a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
+++ b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
@@ -249,8 +249,8 @@
     get() = if (size == 1) first().title else drop(1).joinToString(" > ")
 
 /**
- * Trims the tree of [Demo]s represented by this [DemoCategory] by cutting all leave demos and
- * removing all empty categories as a result.
+ * Trims the tree of [Demo]s represented by this [DemoCategory] by cutting all leave demos for
+ * which the [predicate] returns `false` and recursively removing all empty categories as a result.
  */
 private fun DemoCategory.filter(
     path: List<DemoCategory> = emptyList(),
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index b8e9fd8..d608c2a 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -338,13 +338,17 @@
   }
 
   public final class FloatingActionButtonConstants {
-    method @androidx.compose.runtime.Composable public float animateDefaultElevation-PzTOHyI(androidx.compose.foundation.InteractionState interactionState, optional float defaultElevation, optional float pressedElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
     field public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
   }
 
+  @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface FloatingActionButtonElevation {
+    method public float elevation-D9Ej5fM(androidx.compose.foundation.InteractionState interactionState);
+  }
+
   public final class FloatingActionButtonKt {
-    method @androidx.compose.runtime.Composable public static void ExtendedFloatingActionButton-SrDf79w(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional float elevation);
-    method @androidx.compose.runtime.Composable public static void FloatingActionButton-pvsAfIA(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional float elevation, kotlin.jvm.functions.Function0<kotlin.Unit> icon);
+    method @androidx.compose.runtime.Composable public static void ExtendedFloatingActionButton-i36UMrA(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional androidx.compose.material.FloatingActionButtonElevation elevation);
+    method @androidx.compose.runtime.Composable public static void FloatingActionButton-lf3tHAI(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional androidx.compose.material.FloatingActionButtonElevation elevation, kotlin.jvm.functions.Function0<kotlin.Unit> icon);
   }
 
   @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public final class FractionalThreshold implements androidx.compose.material.ThresholdConfig {
@@ -614,8 +618,18 @@
     method public <T> androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.SwipeableState<T>,T> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmStateChange);
   }
 
+  @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface SwitchColors {
+    method public long thumbColor-0d7_KjU(boolean enabled, boolean checked);
+    method public long trackColor-0d7_KjU(boolean enabled, boolean checked);
+  }
+
+  public final class SwitchConstants {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field public static final androidx.compose.material.SwitchConstants INSTANCE;
+  }
+
   public final class SwitchKt {
-    method @androidx.compose.runtime.Composable public static void Switch-vndggBo(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional long color);
+    method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.material.SwitchColors colors);
   }
 
   public final class TabConstants {
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index b8e9fd8..d608c2a 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -338,13 +338,17 @@
   }
 
   public final class FloatingActionButtonConstants {
-    method @androidx.compose.runtime.Composable public float animateDefaultElevation-PzTOHyI(androidx.compose.foundation.InteractionState interactionState, optional float defaultElevation, optional float pressedElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
     field public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
   }
 
+  @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface FloatingActionButtonElevation {
+    method public float elevation-D9Ej5fM(androidx.compose.foundation.InteractionState interactionState);
+  }
+
   public final class FloatingActionButtonKt {
-    method @androidx.compose.runtime.Composable public static void ExtendedFloatingActionButton-SrDf79w(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional float elevation);
-    method @androidx.compose.runtime.Composable public static void FloatingActionButton-pvsAfIA(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional float elevation, kotlin.jvm.functions.Function0<kotlin.Unit> icon);
+    method @androidx.compose.runtime.Composable public static void ExtendedFloatingActionButton-i36UMrA(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional androidx.compose.material.FloatingActionButtonElevation elevation);
+    method @androidx.compose.runtime.Composable public static void FloatingActionButton-lf3tHAI(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional androidx.compose.material.FloatingActionButtonElevation elevation, kotlin.jvm.functions.Function0<kotlin.Unit> icon);
   }
 
   @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public final class FractionalThreshold implements androidx.compose.material.ThresholdConfig {
@@ -614,8 +618,18 @@
     method public <T> androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.SwipeableState<T>,T> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmStateChange);
   }
 
+  @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface SwitchColors {
+    method public long thumbColor-0d7_KjU(boolean enabled, boolean checked);
+    method public long trackColor-0d7_KjU(boolean enabled, boolean checked);
+  }
+
+  public final class SwitchConstants {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field public static final androidx.compose.material.SwitchConstants INSTANCE;
+  }
+
   public final class SwitchKt {
-    method @androidx.compose.runtime.Composable public static void Switch-vndggBo(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional long color);
+    method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.material.SwitchColors colors);
   }
 
   public final class TabConstants {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index b8e9fd8..d608c2a 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -338,13 +338,17 @@
   }
 
   public final class FloatingActionButtonConstants {
-    method @androidx.compose.runtime.Composable public float animateDefaultElevation-PzTOHyI(androidx.compose.foundation.InteractionState interactionState, optional float defaultElevation, optional float pressedElevation);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
     field public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
   }
 
+  @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface FloatingActionButtonElevation {
+    method public float elevation-D9Ej5fM(androidx.compose.foundation.InteractionState interactionState);
+  }
+
   public final class FloatingActionButtonKt {
-    method @androidx.compose.runtime.Composable public static void ExtendedFloatingActionButton-SrDf79w(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional float elevation);
-    method @androidx.compose.runtime.Composable public static void FloatingActionButton-pvsAfIA(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional float elevation, kotlin.jvm.functions.Function0<kotlin.Unit> icon);
+    method @androidx.compose.runtime.Composable public static void ExtendedFloatingActionButton-i36UMrA(kotlin.jvm.functions.Function0<kotlin.Unit> text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional androidx.compose.material.FloatingActionButtonElevation elevation);
+    method @androidx.compose.runtime.Composable public static void FloatingActionButton-lf3tHAI(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.ui.graphics.Shape shape, optional long backgroundColor, optional long contentColor, optional androidx.compose.material.FloatingActionButtonElevation elevation, kotlin.jvm.functions.Function0<kotlin.Unit> icon);
   }
 
   @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public final class FractionalThreshold implements androidx.compose.material.ThresholdConfig {
@@ -614,8 +618,18 @@
     method public <T> androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.SwipeableState<T>,T> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> confirmStateChange);
   }
 
+  @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface SwitchColors {
+    method public long thumbColor-0d7_KjU(boolean enabled, boolean checked);
+    method public long trackColor-0d7_KjU(boolean enabled, boolean checked);
+  }
+
+  public final class SwitchConstants {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field public static final androidx.compose.material.SwitchConstants INSTANCE;
+  }
+
   public final class SwitchKt {
-    method @androidx.compose.runtime.Composable public static void Switch-vndggBo(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional long color);
+    method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.material.SwitchColors colors);
   }
 
   public final class TabConstants {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
index d21c220..53c306b36 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
@@ -182,7 +182,9 @@
                     FloatingActionButton(
                         modifier = Modifier.testTag("myButton"),
                         onClick = {},
-                        elevation = 0.dp
+                        elevation = FloatingActionButtonConstants.defaultElevation(
+                            defaultElevation = 0.dp
+                        )
                     ) {
                         Box(Modifier.preferredSize(10.dp, 10.dp))
                     }
@@ -216,7 +218,9 @@
                     ExtendedFloatingActionButton(
                         modifier = Modifier.testTag("myButton"),
                         onClick = {},
-                        elevation = 0.dp,
+                        elevation = FloatingActionButtonConstants.defaultElevation(
+                            defaultElevation = 0.dp
+                        ),
                         text = { Box(Modifier.preferredSize(10.dp, 50.dp)) }
                     )
                 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
index f58903e..27d114c 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
@@ -93,7 +93,11 @@
     fun switchTest_checked_customColor() {
         rule.setMaterialContent {
             Box(wrapperModifier) {
-                Switch(checked = true, onCheckedChange = { }, color = Color.Red)
+                Switch(
+                    checked = true,
+                    onCheckedChange = { },
+                    colors = SwitchConstants.defaultColors(checkedThumbColor = Color.Red)
+                )
             }
         }
         assertToggeableAgainstGolden("switch_checked_customColor")
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
index c8df7bb..ce86b3e 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
@@ -60,8 +60,8 @@
  *
  * Typically you should use [animateElevation] instead, which uses these [AnimationSpec]s
  * internally. [animateElevation] in turn is used by the defaults for [Button] and
- * [FloatingActionButton] - inside [ButtonConstants.animateDefaultElevation] and
- * [FloatingActionButtonConstants.animateDefaultElevation] respectively.
+ * [FloatingActionButton] - inside [ButtonConstants.defaultElevation] and
+ * [FloatingActionButtonConstants.defaultElevation] respectively.
  *
  * @see animateElevation
  */
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
index 9833bc6..fca7d76 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
@@ -16,8 +16,11 @@
 
 package androidx.compose.material
 
+import androidx.compose.animation.AnimatedValueModel
 import androidx.compose.animation.VectorConverter
-import androidx.compose.animation.animatedValue
+import androidx.compose.animation.asDisposableClock
+import androidx.compose.animation.core.AnimationClockObservable
+import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.foundation.Interaction
 import androidx.compose.foundation.InteractionState
 import androidx.compose.foundation.ProvideTextStyle
@@ -33,12 +36,13 @@
 import androidx.compose.foundation.layout.preferredWidth
 import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.onCommit
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.AnimationClockAmbient
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 
@@ -62,11 +66,11 @@
  * @param shape The [Shape] of this FAB
  * @param backgroundColor The background color. Use [Color.Transparent] to have no color
  * @param contentColor The preferred content color for content inside this FAB
- * @param elevation The z-coordinate at which to place this FAB. This controls the size
- * of the shadow below the FAB. See [FloatingActionButtonConstants.animateDefaultElevation] for
- * the default elevation that animates between [Interaction]s.
+ * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB
+ * in different states. This controls the size of the shadow below the FAB.
  * @param icon the content of this FAB
  */
+@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun FloatingActionButton(
     onClick: () -> Unit,
@@ -75,7 +79,7 @@
     shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
     backgroundColor: Color = MaterialTheme.colors.secondary,
     contentColor: Color = contentColorFor(backgroundColor),
-    elevation: Dp = FloatingActionButtonConstants.animateDefaultElevation(interactionState),
+    elevation: FloatingActionButtonElevation = FloatingActionButtonConstants.defaultElevation(),
     icon: @Composable () -> Unit
 ) {
     // TODO(aelias): Avoid manually managing the ripple once http://b/157687898
@@ -90,7 +94,7 @@
         shape = shape,
         color = backgroundColor,
         contentColor = contentColor,
-        elevation = elevation
+        elevation = elevation.elevation(interactionState)
     ) {
         ProvideTextStyle(MaterialTheme.typography.button) {
             Box(
@@ -131,10 +135,10 @@
  * @param shape The [Shape] of this FAB
  * @param backgroundColor The background color. Use [Color.Transparent] to have no color
  * @param contentColor The preferred content color. Will be used by text and iconography
- * @param elevation The z-coordinate at which to place this FAB. This controls the size
- * of the shadow below the button. See [FloatingActionButtonConstants.animateDefaultElevation] for
- * the default elevation that animates between [Interaction]s.
+ * @param elevation [FloatingActionButtonElevation] used to resolve the elevation for this FAB
+ * in different states. This controls the size of the shadow below the FAB.
  */
+@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun ExtendedFloatingActionButton(
     text: @Composable () -> Unit,
@@ -145,7 +149,7 @@
     shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
     backgroundColor: Color = MaterialTheme.colors.secondary,
     contentColor: Color = contentColorFor(backgroundColor),
-    elevation: Dp = FloatingActionButtonConstants.animateDefaultElevation(interactionState)
+    elevation: FloatingActionButtonElevation = FloatingActionButtonConstants.defaultElevation()
 ) {
     FloatingActionButton(
         modifier = modifier.preferredSizeIn(
@@ -180,31 +184,70 @@
 }
 
 /**
+ * Represents the elevation for a floating action button in different states.
+ *
+ * See [FloatingActionButtonConstants.defaultElevation] for the default elevation used in a
+ * [FloatingActionButton] and [ExtendedFloatingActionButton].
+ */
+@ExperimentalMaterialApi
+@Stable
+interface FloatingActionButtonElevation {
+    /**
+     * Represents the elevation used in a floating action button, depending on [interactionState].
+     *
+     * @param interactionState the [InteractionState] for this floating action button
+     */
+    fun elevation(interactionState: InteractionState): Dp
+}
+
+/**
  * Contains the default values used by [FloatingActionButton]
  */
 object FloatingActionButtonConstants {
     // TODO: b/152525426 add support for focused and hovered states
     /**
-     * Represents the default elevation for a button in different [Interaction]s, and how the
-     * elevation animates between them.
+     * Creates a [FloatingActionButtonElevation] that will animate between the provided values
+     * according to the Material specification.
      *
-     * @param interactionState the [InteractionState] for this [FloatingActionButton], representing
-     * the current visual state, such as whether it is [Interaction.Pressed] or not.
-     * @param defaultElevation the elevation to use when the [FloatingActionButton] is has no
+     * @param defaultElevation the elevation to use when the [FloatingActionButton] has no
      * [Interaction]s
      * @param pressedElevation the elevation to use when the [FloatingActionButton] is
      * [Interaction.Pressed].
      */
+    @OptIn(ExperimentalMaterialApi::class)
     @Composable
-    fun animateDefaultElevation(
-        interactionState: InteractionState,
+    fun defaultElevation(
         defaultElevation: Dp = 6.dp,
         pressedElevation: Dp = 12.dp
         // focused: Dp = 8.dp,
         // hovered: Dp = 8.dp,
-    ): Dp {
-        class InteractionHolder(var interaction: Interaction?)
+    ): FloatingActionButtonElevation {
+        val clock = AnimationClockAmbient.current.asDisposableClock()
+        return remember(defaultElevation, pressedElevation, clock) {
+            DefaultFloatingActionButtonElevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                clock = clock
+            )
+        }
+    }
+}
 
+/**
+ * Default [FloatingActionButtonElevation] implementation.
+ */
+@OptIn(ExperimentalMaterialApi::class)
+@Stable
+private class DefaultFloatingActionButtonElevation(
+    private val defaultElevation: Dp,
+    private val pressedElevation: Dp,
+    private val clock: AnimationClockObservable
+) : FloatingActionButtonElevation {
+    private val lazyAnimatedElevation = LazyAnimatedValue<Dp, AnimationVector1D> { target ->
+        AnimatedValueModel(initialValue = target, typeConverter = Dp.VectorConverter, clock = clock)
+    }
+
+    override fun elevation(interactionState: InteractionState): Dp {
         val interaction = interactionState.value.lastOrNull {
             it is Interaction.Pressed
         }
@@ -214,20 +257,18 @@
             else -> defaultElevation
         }
 
-        val previousInteractionHolder = remember { InteractionHolder(interaction) }
+        val animatedElevation = lazyAnimatedElevation.animatedValueForTarget(target)
 
-        val animatedElevation = animatedValue(target, Dp.VectorConverter)
-
-        onCommit(target) {
+        if (animatedElevation.targetValue != target) {
+            val lastInteraction = when (animatedElevation.targetValue) {
+                pressedElevation -> Interaction.Pressed
+                else -> null
+            }
             animatedElevation.animateElevation(
-                from = previousInteractionHolder.interaction,
+                from = lastInteraction,
                 to = interaction,
                 target = target
             )
-
-            // Update the last interaction, so we know what AnimationSpec to use if we animate
-            // away from a state
-            previousInteractionHolder.interaction = interaction
         }
 
         return animatedElevation.value
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
index c36fb53..822b37a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
@@ -32,7 +32,10 @@
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.ripple.RippleIndication
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.emptyContent
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -61,7 +64,8 @@
  * present on this Switch. You can create and pass in your own remembered
  * [InteractionState] if you want to read the [InteractionState] and customize the appearance /
  * behavior of this Switch in different [Interaction]s.
- * @param color main color of the track and thumb when the Switch is checked
+ * @param colors [SwitchColors] that will be used to determine the color of the thumb and track
+ * in different states. See [SwitchConstants.defaultColors].
  */
 @Composable
 @OptIn(ExperimentalMaterialApi::class)
@@ -71,7 +75,7 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     interactionState: InteractionState = remember { InteractionState() },
-    color: Color = MaterialTheme.colors.secondaryVariant
+    colors: SwitchColors = SwitchConstants.defaultColors()
 ) {
     val minBound = 0f
     val maxBound = with(DensityAmbient.current) { ThumbPathLength.toPx() }
@@ -103,34 +107,61 @@
         SwitchImpl(
             checked = checked,
             enabled = enabled,
-            checkedColor = color,
+            colors = colors,
             thumbValue = swipeableState.offset,
             interactionState = interactionState
         )
     }
 }
 
+/**
+ * Represents the colors used by a [Switch] in different states
+ *
+ * See [SwitchConstants.defaultColors] for the default implementation that follows Material
+ * specifications.
+ */
+@ExperimentalMaterialApi
+@Stable
+interface SwitchColors {
+
+    /**
+     * Represents the color used for the switch's thumb, depending on [enabled] and [checked].
+     *
+     * @param enabled whether the [Switch] is enabled or not
+     * @param checked whether the [Switch] is checked or not
+     */
+    fun thumbColor(enabled: Boolean, checked: Boolean): Color
+
+    /**
+     * Represents the color used for the switch's track, depending on [enabled] and [checked].
+     *
+     * @param enabled whether the [Switch] is enabled or not
+     * @param checked whether the [Switch] is checked or not
+     */
+    fun trackColor(enabled: Boolean, checked: Boolean): Color
+}
+
+@OptIn(ExperimentalMaterialApi::class)
 @Composable
 private fun BoxScope.SwitchImpl(
     checked: Boolean,
     enabled: Boolean,
-    checkedColor: Color,
+    colors: SwitchColors,
     thumbValue: State<Float>,
     interactionState: InteractionState
 ) {
     val hasInteraction =
         Interaction.Pressed in interactionState || Interaction.Dragged in interactionState
-    val elevation =
-        if (hasInteraction) {
-            SwitchDefaults.ThumbPressedElevation
-        } else {
-            SwitchDefaults.ThumbDefaultElevation
-        }
-    val trackColor = SwitchDefaults.resolveTrackColor(checked, enabled, checkedColor)
-    val thumbColor = SwitchDefaults.resolveThumbColor(checked, enabled, checkedColor)
+    val elevation = if (hasInteraction) {
+        ThumbPressedElevation
+    } else {
+        ThumbDefaultElevation
+    }
     Canvas(Modifier.align(Alignment.Center).fillMaxSize()) {
+        val trackColor = colors.trackColor(enabled, checked)
         drawTrack(trackColor, TrackWidth.toPx(), TrackStrokeWidth.toPx())
     }
+    val thumbColor = colors.thumbColor(enabled, checked)
     Surface(
         shape = CircleShape,
         color = thumbColor,
@@ -142,8 +173,9 @@
                 interactionState = interactionState,
                 indication = RippleIndication(radius = ThumbRippleRadius, bounded = false)
             )
-            .size(ThumbDiameter)
-    ) {}
+            .size(ThumbDiameter),
+        content = emptyContent()
+    )
 }
 
 private fun DrawScope.drawTrack(trackColor: Color, trackWidth: Float, strokeWidth: Float) {
@@ -170,61 +202,121 @@
 
 private val AnimationSpec = TweenSpec<Float>(durationMillis = 100)
 
-internal object SwitchDefaults {
+private val ThumbDefaultElevation = 1.dp
+private val ThumbPressedElevation = 6.dp
 
-    const val CheckedTrackOpacity = 0.54f
-    const val UncheckedTrackOpacity = 0.38f
-
-    val ThumbDefaultElevation = 1.dp
-    val ThumbPressedElevation = 6.dp
-
+/**
+ * Contains the default values used by [Switch]
+ */
+object SwitchConstants {
+    /**
+     * Creates a [SwitchColors] that represents the different colors used in a [Switch] in
+     * different states.
+     *
+     * @param checkedThumbColor the color used for the thumb when enabled and checked
+     * @param checkedTrackColor the color used for the track when enabled and checked
+     * @param checkedTrackAlpha the alpha applied to [checkedTrackColor] and
+     * [disabledCheckedTrackColor]
+     * @param uncheckedThumbColor the color used for the thumb when enabled and unchecked
+     * @param uncheckedTrackColor the color used for the track when enabled and unchecked
+     * @param uncheckedTrackAlpha the alpha applied to [uncheckedTrackColor] and
+     * [disabledUncheckedTrackColor]
+     * @param disabledCheckedThumbColor the color used for the thumb when disabled and checked
+     * @param disabledCheckedTrackColor the color used for the track when disabled and checked
+     * @param disabledUncheckedThumbColor the color used for the thumb when disabled and unchecked
+     * @param disabledUncheckedTrackColor the color used for the track when disabled and unchecked
+     */
+    @OptIn(ExperimentalMaterialApi::class)
     @Composable
-    private val uncheckedTrackColor
-        get() = MaterialTheme.colors.onSurface
-
-    @Composable
-    private fun makeDisabledCheckedTrackColor(checkedColor: Color) = AmbientEmphasisLevels.current
-        .disabled
-        .applyEmphasis(checkedColor)
-        .compositeOver(MaterialTheme.colors.surface)
-
-    @Composable
-    private val disabledUncheckedTrackColor
-        get() = AmbientEmphasisLevels.current.disabled.applyEmphasis(MaterialTheme.colors.onSurface)
+    fun defaultColors(
+        checkedThumbColor: Color = MaterialTheme.colors.secondaryVariant,
+        checkedTrackColor: Color = checkedThumbColor,
+        checkedTrackAlpha: Float = 0.54f,
+        uncheckedThumbColor: Color = MaterialTheme.colors.surface,
+        uncheckedTrackColor: Color = MaterialTheme.colors.onSurface,
+        uncheckedTrackAlpha: Float = 0.38f,
+        disabledCheckedThumbColor: Color = AmbientEmphasisLevels.current.disabled
+            .applyEmphasis(checkedThumbColor)
+            .compositeOver(MaterialTheme.colors.surface),
+        disabledCheckedTrackColor: Color = AmbientEmphasisLevels.current.disabled
+            .applyEmphasis(checkedTrackColor)
+            .compositeOver(MaterialTheme.colors.surface),
+        disabledUncheckedThumbColor: Color = AmbientEmphasisLevels.current.disabled
+            .applyEmphasis(uncheckedThumbColor)
+            .compositeOver(MaterialTheme.colors.surface),
+        disabledUncheckedTrackColor: Color = AmbientEmphasisLevels.current.disabled
+            .applyEmphasis(uncheckedTrackColor)
             .compositeOver(MaterialTheme.colors.surface)
+    ): SwitchColors = DefaultSwitchColors(
+        checkedThumbColor = checkedThumbColor,
+        checkedTrackColor = checkedTrackColor.copy(alpha = checkedTrackAlpha),
+        uncheckedThumbColor = uncheckedThumbColor,
+        uncheckedTrackColor = uncheckedTrackColor.copy(alpha = uncheckedTrackAlpha),
+        disabledCheckedThumbColor = disabledCheckedThumbColor,
+        disabledCheckedTrackColor = disabledCheckedTrackColor.copy(alpha = checkedTrackAlpha),
+        disabledUncheckedThumbColor = disabledUncheckedThumbColor,
+        disabledUncheckedTrackColor = disabledUncheckedTrackColor.copy(alpha = uncheckedTrackAlpha)
+    )
+}
 
-    @Composable
-    private val uncheckedThumbColor
-        get() = MaterialTheme.colors.surface
-
-    @Composable
-    private fun makeDisabledCheckedThumbColor(checkedColor: Color) = AmbientEmphasisLevels.current
-        .disabled
-        .applyEmphasis(checkedColor)
-        .compositeOver(MaterialTheme.colors.surface)
-
-    @Composable
-    private val disabledUncheckedThumbColor
-        get() = AmbientEmphasisLevels.current.disabled.applyEmphasis(MaterialTheme.colors.surface)
-            .compositeOver(MaterialTheme.colors.surface)
-
-    @Composable
-    internal fun resolveTrackColor(checked: Boolean, enabled: Boolean, checkedColor: Color): Color {
-        return if (checked) {
-            val color = if (enabled) checkedColor else makeDisabledCheckedTrackColor(checkedColor)
-            color.copy(alpha = CheckedTrackOpacity)
+/**
+ * Default [SwitchColors] implementation.
+ */
+@OptIn(ExperimentalMaterialApi::class)
+@Immutable
+private class DefaultSwitchColors(
+    private val checkedThumbColor: Color,
+    private val checkedTrackColor: Color,
+    private val uncheckedThumbColor: Color,
+    private val uncheckedTrackColor: Color,
+    private val disabledCheckedThumbColor: Color,
+    private val disabledCheckedTrackColor: Color,
+    private val disabledUncheckedThumbColor: Color,
+    private val disabledUncheckedTrackColor: Color
+) : SwitchColors {
+    override fun thumbColor(enabled: Boolean, checked: Boolean): Color {
+        return if (enabled) {
+            if (checked) checkedThumbColor else uncheckedThumbColor
         } else {
-            val color = if (enabled) uncheckedTrackColor else disabledUncheckedTrackColor
-            color.copy(alpha = UncheckedTrackOpacity)
+            if (checked) disabledCheckedThumbColor else disabledUncheckedThumbColor
         }
     }
 
-    @Composable
-    internal fun resolveThumbColor(checked: Boolean, enabled: Boolean, checkedColor: Color): Color {
-        return if (checked) {
-            if (enabled) checkedColor else makeDisabledCheckedThumbColor(checkedColor)
+    override fun trackColor(enabled: Boolean, checked: Boolean): Color {
+        return if (enabled) {
+            if (checked) checkedTrackColor else uncheckedTrackColor
         } else {
-            if (enabled) uncheckedThumbColor else disabledUncheckedThumbColor
+            if (checked) disabledCheckedTrackColor else disabledUncheckedTrackColor
         }
     }
-}
\ No newline at end of file
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as DefaultSwitchColors
+
+        if (checkedThumbColor != other.checkedThumbColor) return false
+        if (checkedTrackColor != other.checkedTrackColor) return false
+        if (uncheckedThumbColor != other.uncheckedThumbColor) return false
+        if (uncheckedTrackColor != other.uncheckedTrackColor) return false
+        if (disabledCheckedThumbColor != other.disabledCheckedThumbColor) return false
+        if (disabledCheckedTrackColor != other.disabledCheckedTrackColor) return false
+        if (disabledUncheckedThumbColor != other.disabledUncheckedThumbColor) return false
+        if (disabledUncheckedTrackColor != other.disabledUncheckedTrackColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = checkedThumbColor.hashCode()
+        result = 31 * result + checkedTrackColor.hashCode()
+        result = 31 * result + uncheckedThumbColor.hashCode()
+        result = 31 * result + uncheckedTrackColor.hashCode()
+        result = 31 * result + disabledCheckedThumbColor.hashCode()
+        result = 31 * result + disabledCheckedTrackColor.hashCode()
+        result = 31 * result + disabledUncheckedThumbColor.hashCode()
+        result = 31 * result + disabledUncheckedTrackColor.hashCode()
+        return result
+    }
+}
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 7a830ff..99045b7 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -1521,9 +1521,6 @@
 package androidx.compose.ui.input.pointer {
 
   public final class ConsumedData {
-    method public long component1-F1C5BW0();
-    method public boolean component2();
-    method public androidx.compose.ui.input.pointer.ConsumedData copy-Dkz3Of0(long positionChange, boolean downChange);
     method public boolean getDownChange();
     method public long getPositionChange-F1C5BW0();
     method public void setDownChange(boolean p);
@@ -2331,9 +2328,6 @@
     method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.selection.SelectionRegistrar> getSelectionRegistrarAmbient();
   }
 
-  public final class SimpleContainerKt {
-  }
-
   public final class SimpleLayoutKt {
   }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 7a830ff..99045b7 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -1521,9 +1521,6 @@
 package androidx.compose.ui.input.pointer {
 
   public final class ConsumedData {
-    method public long component1-F1C5BW0();
-    method public boolean component2();
-    method public androidx.compose.ui.input.pointer.ConsumedData copy-Dkz3Of0(long positionChange, boolean downChange);
     method public boolean getDownChange();
     method public long getPositionChange-F1C5BW0();
     method public void setDownChange(boolean p);
@@ -2331,9 +2328,6 @@
     method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.selection.SelectionRegistrar> getSelectionRegistrarAmbient();
   }
 
-  public final class SimpleContainerKt {
-  }
-
   public final class SimpleLayoutKt {
   }
 
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index d75d3e6..23d9152 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -1568,9 +1568,6 @@
 package androidx.compose.ui.input.pointer {
 
   public final class ConsumedData {
-    method public long component1-F1C5BW0();
-    method public boolean component2();
-    method public androidx.compose.ui.input.pointer.ConsumedData copy-Dkz3Of0(long positionChange, boolean downChange);
     method public boolean getDownChange();
     method public long getPositionChange-F1C5BW0();
     method public void setDownChange(boolean p);
@@ -2408,9 +2405,6 @@
     method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.selection.SelectionRegistrar> getSelectionRegistrarAmbient();
   }
 
-  public final class SimpleContainerKt {
-  }
-
   public final class SimpleLayoutKt {
   }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index c0ad627..365db7b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -41,7 +41,6 @@
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.milliseconds
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.test.filters.SmallTest
@@ -239,7 +238,7 @@
     //  when run from Android Studio.  This seems to be caused by b/158099918.  Once that is
     //  fixed, @Ignore can be removed.
     @Ignore
-    fun dispatchTouchEvent_throughLayersOfAndroidAndCompose_hitsChildPointerInputFilter() {
+    fun dispatchTouchEvent_throughLayersOfAndroidAndCompose_hitsChildWithCorrectCoords() {
 
         // Arrange
 
@@ -297,7 +296,8 @@
 
             // Assert
             assertThat(log).hasSize(1)
-            assertThat(log[0]).isEqualTo(listOf(down(0, 0.milliseconds, 0f, 0f)))
+            assertThat(log[0]).hasSize(1)
+            assertThat(log[0][0].current.position).isEqualTo(Offset(0f, 0f))
         }
     }
 
@@ -417,7 +417,8 @@
 
             // Assert
             assertThat(log).hasSize(1)
-            assertThat(log[0]).isEqualTo(listOf(down(0, 0.milliseconds, 0f, 0f)))
+            assertThat(log[0]).hasSize(1)
+            assertThat(log[0][0].current.position).isEqualTo(Offset(0f, 0f))
         }
     }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index 0fb2a00..388eef5 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -237,7 +237,9 @@
         assertThat(log).hasSize(3)
         // Verify call values
         PointerEventPass.values().forEachIndexed { index, value ->
-            assertThat(log[index].pointerEvent).isEqualTo(pointerEventOf(down(13)))
+            PointerEventSubject
+                .assertThat(log[index].pointerEvent)
+                .isStructurallyEqualTo(pointerEventOf(down(13)))
             assertThat(log[index].pass).isEqualTo(value)
         }
     }
@@ -259,7 +261,9 @@
         assertThat(onPointerEventLog[1].pointerInputFilter).isEqualTo(pif2)
         assertThat(onPointerEventLog[2].pointerInputFilter).isEqualTo(pif3)
         onPointerEventLog.forEach {
-            assertThat(it.pointerEvent).isEqualTo(pointerEventOf(down(13)))
+            PointerEventSubject
+                .assertThat(it.pointerEvent)
+                .isStructurallyEqualTo(pointerEventOf(down(13)))
         }
     }
 
@@ -284,7 +288,9 @@
         assertThat(onPointerEventLog[4].pointerInputFilter).isEqualTo(pif2)
         assertThat(onPointerEventLog[5].pointerInputFilter).isEqualTo(pif1)
         onPointerEventLog.forEach {
-            assertThat(it.pointerEvent).isEqualTo(pointerEventOf(down(13)))
+            PointerEventSubject
+                .assertThat(it.pointerEvent)
+                .isStructurallyEqualTo(pointerEventOf(down(13)))
         }
     }
 
@@ -317,7 +323,9 @@
         assertThat(log2).hasSize(4)
 
         log1.forEach {
-            assertThat(it.pointerEvent).isEqualTo(pointerEventOf(event1))
+            PointerEventSubject
+                .assertThat(it.pointerEvent)
+                .isStructurallyEqualTo(pointerEventOf(event1))
         }
 
         assertThat(log1[0].pointerInputFilter).isEqualTo(pif1)
@@ -368,36 +376,52 @@
 
         // Verifies that the events traverse between parent and child1 in the correct order.
         assertThat(log1[0].pointerInputFilter).isEqualTo(parent)
-        assertThat(log1[0].pointerEvent).isEqualTo(pointerEventOf(event1, event2))
+        PointerEventSubject
+            .assertThat(log1[0].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1, event2))
         assertThat(log1[0].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[1].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[1].pointerEvent).isEqualTo(pointerEventOf(event1))
+        PointerEventSubject
+            .assertThat(log1[1].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1))
         assertThat(log1[1].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[2].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[2].pointerEvent).isEqualTo(pointerEventOf(event1))
+        PointerEventSubject
+            .assertThat(log1[2].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1))
         assertThat(log1[2].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[3].pointerInputFilter).isEqualTo(parent)
-        assertThat(log1[3].pointerEvent).isEqualTo(pointerEventOf(event1, event2))
+        PointerEventSubject
+            .assertThat(log1[3].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1, event2))
         assertThat(log1[3].pass).isEqualTo(PointerEventPass.Main)
 
         // Verifies that the events traverse between parent and child2 in the correct order.
         assertThat(log1[0].pointerInputFilter).isEqualTo(parent)
-        assertThat(log1[0].pointerEvent).isEqualTo(pointerEventOf(event1, event2))
+        PointerEventSubject
+            .assertThat(log1[0].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1, event2))
         assertThat(log1[0].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[1].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[1].pointerEvent).isEqualTo(pointerEventOf(event1))
+        PointerEventSubject
+            .assertThat(log1[1].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1))
         assertThat(log1[1].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[2].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[2].pointerEvent).isEqualTo(pointerEventOf(event1))
+        PointerEventSubject
+            .assertThat(log1[2].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1))
         assertThat(log1[2].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[3].pointerInputFilter).isEqualTo(parent)
-        assertThat(log1[3].pointerEvent).isEqualTo(pointerEventOf(event1, event2))
+        PointerEventSubject
+            .assertThat(log1[3].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(event1, event2))
         assertThat(log1[3].pass).isEqualTo(PointerEventPass.Main)
     }
 
@@ -424,7 +448,9 @@
 
         // Verify PointerEvent
         log1.forEach {
-            assertThat(it.pointerEvent).isEqualTo(pointerEventOf(event1, event2))
+            PointerEventSubject
+                .assertThat(it.pointerEvent)
+                .isStructurallyEqualTo(pointerEventOf(event1, event2))
         }
 
         // Verify dispatch order
@@ -442,7 +468,9 @@
     fun dispatchChanges_noNodes_nothingChanges() {
         val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(5)))
 
-        assertThat(result.changes.values.first()).isEqualTo(down(5))
+        PointerInputChangeSubject
+            .assertThat(result.changes.values.first())
+            .isStructurallyEqualTo(down(5))
     }
 
     @Test
@@ -460,8 +488,9 @@
 
         val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
-        assertThat(result.changes.values.first())
-            .isEqualTo(down(13).apply { consumeDownChange() })
+        PointerInputChangeSubject
+            .assertThat(result.changes.values.first())
+            .isStructurallyEqualTo(down(13).apply { consumeDownChange() })
     }
 
     @Test
@@ -525,51 +554,66 @@
             .filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
 
         assertThat(log1[0].pointerInputFilter).isEqualTo(pif1)
-        assertThat(log1[0].pointerEvent).isEqualTo(pointerEventOf(expectedChange))
+        PointerEventSubject
+            .assertThat(log1[0].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(expectedChange))
         assertThat(log1[0].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[1].pointerInputFilter).isEqualTo(pif2)
-        assertThat(log1[1].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedChange.apply { consumePositionChange(0f, 1f) }
+        PointerEventSubject
+            .assertThat(log1[1].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedChange.apply { consumePositionChange(0f, 1f) }
+                )
             )
-        )
         assertThat(log1[1].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[2].pointerInputFilter).isEqualTo(pif3)
-        assertThat(log1[2].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedChange.apply { consumePositionChange(0f, 2f) }
+        PointerEventSubject
+            .assertThat(log1[2].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedChange.apply { consumePositionChange(0f, 2f) }
+                )
             )
-        )
         assertThat(log1[2].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[3].pointerInputFilter).isEqualTo(pif3)
-        assertThat(log1[3].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedChange.apply { consumePositionChange(0f, 3f) }
+        PointerEventSubject
+            .assertThat(log1[3].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedChange.apply { consumePositionChange(0f, 3f) }
+                )
             )
-        )
         assertThat(log1[3].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[4].pointerInputFilter).isEqualTo(pif2)
-        assertThat(log1[4].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedChange.apply { consumePositionChange(0f, 4f) }
+        PointerEventSubject
+            .assertThat(log1[4].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedChange.apply { consumePositionChange(0f, 4f) }
+                )
             )
-        )
         assertThat(log1[4].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[5].pointerInputFilter).isEqualTo(pif1)
-        assertThat(log1[5].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedChange.apply { consumePositionChange(0f, 5f) }
+        PointerEventSubject
+            .assertThat(log1[5].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedChange.apply { consumePositionChange(0f, 5f) }
+                )
             )
-        )
         assertThat(log1[5].pass).isEqualTo(PointerEventPass.Main)
 
-        assertThat(result.changes.values.first())
-            .isEqualTo(expectedChange.apply { consumePositionChange(0f, 6f) })
+        PointerInputChangeSubject
+            .assertThat(result.changes.values.first())
+            .isStructurallyEqualTo(
+                expectedChange.apply { consumePositionChange(0f, 6f) }
+            )
     }
 
     @Test
@@ -659,68 +703,88 @@
             .filter { it.pointerInputFilter == pif3 || it.pointerInputFilter == pif4 }
 
         assertThat(log1[0].pointerInputFilter).isEqualTo(pif1)
-        assertThat(log1[0].pointerEvent).isEqualTo(pointerEventOf(expectedEvent1))
+        PointerEventSubject
+            .assertThat(log1[0].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(expectedEvent1))
         assertThat(log1[0].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[1].pointerInputFilter).isEqualTo(pif2)
-        assertThat(log1[1].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 1f) }
+        PointerEventSubject
+            .assertThat(log1[1].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 1f) }
+                )
             )
-        )
         assertThat(log1[1].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[2].pointerInputFilter).isEqualTo(pif2)
-        assertThat(log1[2].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 2f) }
+        PointerEventSubject
+            .assertThat(log1[2].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 2f) }
+                )
             )
-        )
         assertThat(log1[2].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[3].pointerInputFilter).isEqualTo(pif1)
-        assertThat(log1[3].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 3f) }
+        PointerEventSubject
+            .assertThat(log1[3].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 3f) }
+                )
             )
-        )
         assertThat(log1[3].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log2[0].pointerInputFilter).isEqualTo(pif3)
-        assertThat(log2[0].pointerEvent).isEqualTo(pointerEventOf(expectedEvent2))
+        PointerEventSubject
+            .assertThat(log2[0].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(expectedEvent2))
         assertThat(log2[0].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log2[1].pointerInputFilter).isEqualTo(pif4)
-        assertThat(log2[1].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent2.apply { consumePositionChange(0f, -1f) }
+        PointerEventSubject
+            .assertThat(log2[1].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent2.apply { consumePositionChange(0f, -1f) }
+                )
             )
-        )
         assertThat(log2[1].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log2[2].pointerInputFilter).isEqualTo(pif4)
-        assertThat(log2[2].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent2.apply { consumePositionChange(0f, -2f) }
+        PointerEventSubject
+            .assertThat(log2[2].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent2.apply { consumePositionChange(0f, -2f) }
+                )
             )
-        )
         assertThat(log2[2].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log2[3].pointerInputFilter).isEqualTo(pif3)
-        assertThat(log2[3].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent2.apply { consumePositionChange(0f, -3f) }
+        PointerEventSubject
+            .assertThat(log2[3].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent2.apply { consumePositionChange(0f, -3f) }
+                )
             )
-        )
         assertThat(log2[3].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(result.changes).hasSize(2)
-        assertThat(result.changes[actualEvent1.id]).isEqualTo(
-            expectedEvent1.apply { consumePositionChange(0f, 4f) }
-        )
-        assertThat(result.changes[actualEvent2.id]).isEqualTo(
-            expectedEvent2.apply { consumePositionChange(0f, -4f) }
-        )
+        PointerInputChangeSubject
+            .assertThat(result.changes[actualEvent1.id])
+            .isStructurallyEqualTo(
+                expectedEvent1.apply { consumePositionChange(0f, 4f) }
+            )
+        PointerInputChangeSubject
+            .assertThat(result.changes[actualEvent2.id])
+            .isStructurallyEqualTo(
+                expectedEvent2.apply { consumePositionChange(0f, -4f) }
+            )
     }
 
     @Test
@@ -801,57 +865,73 @@
             .filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
 
         assertThat(log1[0].pointerInputFilter).isEqualTo(parent)
-        assertThat(log1[0].pointerEvent).isEqualTo(pointerEventOf(expectedEvent1, expectedEvent2))
+        PointerEventSubject
+            .assertThat(log1[0].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(expectedEvent1, expectedEvent2))
         assertThat(log1[0].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[1].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[1].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 1f) }
+        PointerEventSubject
+            .assertThat(log1[1].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 1f) }
+                )
             )
-        )
         assertThat(log1[1].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[2].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[2].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 2f) }
+        PointerEventSubject
+            .assertThat(log1[2].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 2f) }
+                )
             )
-        )
         assertThat(log1[2].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[3].pointerInputFilter).isEqualTo(child2)
-        assertThat(log1[3].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent2.apply { consumePositionChange(0f, 1f) }
+        PointerEventSubject
+            .assertThat(log1[3].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent2.apply { consumePositionChange(0f, 1f) }
+                )
             )
-        )
         assertThat(log1[3].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[4].pointerInputFilter).isEqualTo(child2)
-        assertThat(log1[4].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent2.apply { consumePositionChange(0f, 4f) }
+        PointerEventSubject
+            .assertThat(log1[4].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent2.apply { consumePositionChange(0f, 4f) }
+                )
             )
-        )
         assertThat(log1[4].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[5].pointerInputFilter).isEqualTo(parent)
-        assertThat(log1[5].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 20f) },
-                expectedEvent2.apply { consumePositionChange(0f, 40f) }
+        PointerEventSubject
+            .assertThat(log1[5].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 20f) },
+                    expectedEvent2.apply { consumePositionChange(0f, 40f) }
+                )
             )
-        )
         assertThat(log1[5].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(result.changes).hasSize(2)
-        assertThat(result.changes[actualEvent1.id]).isEqualTo(
-            expectedEvent1.apply { consumePositionChange(0f, 10f) }
-        )
-        assertThat(result.changes[actualEvent2.id]).isEqualTo(
-            expectedEvent2.apply { consumePositionChange(0f, 10f) }
-        )
+        PointerInputChangeSubject
+            .assertThat(result.changes[actualEvent1.id])
+            .isStructurallyEqualTo(
+                expectedEvent1.apply { consumePositionChange(0f, 10f) }
+            )
+        PointerInputChangeSubject
+            .assertThat(result.changes[actualEvent2.id])
+            .isStructurallyEqualTo(
+                expectedEvent2.apply { consumePositionChange(0f, 10f) }
+            )
     }
 
     @Test
@@ -911,43 +991,55 @@
             .filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
 
         assertThat(log1[0].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[0].pointerEvent).isEqualTo(pointerEventOf(expectedEvent1, expectedEvent2))
+        PointerEventSubject
+            .assertThat(log1[0].pointerEvent)
+            .isStructurallyEqualTo(pointerEventOf(expectedEvent1, expectedEvent2))
         assertThat(log1[0].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[1].pointerInputFilter).isEqualTo(child2)
-        assertThat(log1[1].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 1f) },
-                expectedEvent2.apply { consumePositionChange(0f, 1f) }
+        PointerEventSubject
+            .assertThat(log1[1].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 1f) },
+                    expectedEvent2.apply { consumePositionChange(0f, 1f) }
+                )
             )
-        )
         assertThat(log1[1].pass).isEqualTo(PointerEventPass.Initial)
 
         assertThat(log1[2].pointerInputFilter).isEqualTo(child2)
-        assertThat(log1[2].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 2f) },
-                expectedEvent2.apply { consumePositionChange(0f, 2f) }
+        PointerEventSubject
+            .assertThat(log1[2].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 2f) },
+                    expectedEvent2.apply { consumePositionChange(0f, 2f) }
+                )
             )
-        )
         assertThat(log1[2].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(log1[3].pointerInputFilter).isEqualTo(child1)
-        assertThat(log1[3].pointerEvent).isEqualTo(
-            pointerEventOf(
-                expectedEvent1.apply { consumePositionChange(0f, 3f) },
-                expectedEvent2.apply { consumePositionChange(0f, 3f) }
+        PointerEventSubject
+            .assertThat(log1[3].pointerEvent)
+            .isStructurallyEqualTo(
+                pointerEventOf(
+                    expectedEvent1.apply { consumePositionChange(0f, 3f) },
+                    expectedEvent2.apply { consumePositionChange(0f, 3f) }
+                )
             )
-        )
         assertThat(log1[3].pass).isEqualTo(PointerEventPass.Main)
 
         assertThat(result.changes).hasSize(2)
-        assertThat(result.changes[actualEvent1.id]).isEqualTo(
-            expectedEvent1.apply { consumePositionChange(0f, 4f) }
-        )
-        assertThat(result.changes[actualEvent2.id]).isEqualTo(
-            expectedEvent2.apply { consumePositionChange(0f, 4f) }
-        )
+        PointerInputChangeSubject
+            .assertThat(result.changes[actualEvent1.id])
+            .isStructurallyEqualTo(
+                expectedEvent1.apply { consumePositionChange(0f, 4f) }
+            )
+        PointerInputChangeSubject
+            .assertThat(result.changes[actualEvent2.id])
+            .isStructurallyEqualTo(
+                expectedEvent2.apply { consumePositionChange(0f, 4f) }
+            )
     }
 
     @Test
@@ -3578,7 +3670,9 @@
 
         assertThat(log1).hasSize(6)
         log1.forEach {
-            assertThat(it.pointerEvent).isEqualTo(expected)
+            PointerEventSubject
+                .assertThat(it.pointerEvent)
+                .isStructurallyEqualTo(expected)
         }
     }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index e2e5c19..a287a55 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -51,13 +51,7 @@
 import androidx.compose.ui.unit.minus
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.eq
-import com.nhaarman.mockitokotlin2.inOrder
-import com.nhaarman.mockitokotlin2.never
 import com.nhaarman.mockitokotlin2.spy
-import com.nhaarman.mockitokotlin2.times
-import com.nhaarman.mockitokotlin2.verify
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -106,8 +100,7 @@
     fun process_downMoveUp_convertedCorrectlyAndTraversesAllPassesInCorrectOrder() {
 
         // Arrange
-
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0,
             0,
@@ -126,53 +119,14 @@
         val events = arrayOf(
             PointerInputEvent(8712, Uptime.Boot + 3.milliseconds, offset, true),
             PointerInputEvent(8712, Uptime.Boot + 11.milliseconds, offset2, true),
-            PointerInputEvent(8712, Uptime.Boot + 13.milliseconds, offset2, false)
+            PointerInputEvent(8712, Uptime.Boot + 13.milliseconds, null, false)
         )
 
-        val expectedChanges = arrayOf(
-            PointerInputChange(
-                id = PointerId(8712),
-                current = PointerInputData(
-                    Uptime.Boot + 3.milliseconds,
-                    offset,
-                    true
-                ),
-                previous = PointerInputData(
-                    null,
-                    null,
-                    false
-                ),
-                consumed = ConsumedData()
-            ),
-            PointerInputChange(
-                id = PointerId(8712),
-                current = PointerInputData(
-                    Uptime.Boot + 11.milliseconds,
-                    offset2,
-                    true
-                ),
-                previous = PointerInputData(
-                    Uptime.Boot + 3.milliseconds,
-                    offset,
-                    true
-                ),
-                consumed = ConsumedData()
-            ),
-            PointerInputChange(
-                id = PointerId(8712),
-                current = PointerInputData(
-                    Uptime.Boot + 13.milliseconds,
-                    offset2,
-                    false
-                ),
-                previous = PointerInputData(
-                    Uptime.Boot + 11.milliseconds,
-                    offset2,
-                    true
-                ),
-                consumed = ConsumedData()
-            )
-        )
+        val down = down(8712, 3.milliseconds, offset.x, offset.y)
+        val move = down.moveTo(11.milliseconds, offset2.x, offset2.y)
+        val up = move.up(13.milliseconds)
+
+        val expectedChanges = arrayOf(down, move, up)
 
         // Act
 
@@ -180,22 +134,22 @@
 
         // Assert
 
+        val log = pointerInputFilter.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(
-            pointerInputFilter,
-            times(PointerEventPass.values().size * expectedChanges.size)
-        ).onPointerEventMock(any(), any(), any())
+        assertThat(log)
+            .hasSize(PointerEventPass.values().size * expectedChanges.size)
 
         // Verify call values
-        inOrder(pointerInputFilter) {
-            for (expected in expectedChanges) {
-                for (pass in PointerEventPass.values()) {
-                    verify(pointerInputFilter).onPointerEventMock(
-                        eq(pointerEventOf(expected)),
-                        eq(pass),
-                        any()
-                    )
-                }
+        var count = 0
+        expectedChanges.forEach { change ->
+            PointerEventPass.values().forEach { pass ->
+                val item = log[count]
+                PointerEventSubject
+                    .assertThat(item.pointerEvent)
+                    .isStructurallyEqualTo(pointerEventOf(change))
+                assertThat(item.pass).isEqualTo(pass)
+                count++
             }
         }
     }
@@ -206,7 +160,7 @@
         // Arrange
 
         val childOffset = Offset(100f, 200f)
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             100, 200, 301, 401,
             PointerInputModifierImpl2(
@@ -252,19 +206,22 @@
 
         // Assert
 
+        val log =
+            pointerInputFilter
+                .log
+                .getOnPointerEventLog()
+                .filter { it.pass == PointerEventPass.Initial }
+
         // Verify call count
-        verify(pointerInputFilter, times(expectedChanges.size)).onPointerEventMock(
-            any(),
-            eq(PointerEventPass.Initial),
-            any()
-        )
+        assertThat(log)
+            .hasSize(expectedChanges.size)
+
         // Verify call values
-        for (expected in expectedChanges) {
-            verify(pointerInputFilter).onPointerEventMock(
-                eq(pointerEventOf(expected)),
-                eq(PointerEventPass.Initial),
-                any()
-            )
+        expectedChanges.forEachIndexed { index, change ->
+            val item = log[index]
+            PointerEventSubject
+                .assertThat(item.pointerEvent)
+                .isStructurallyEqualTo(pointerEventOf(change))
         }
     }
 
@@ -273,7 +230,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             100, 200, 301, 401,
             PointerInputModifierImpl2(
@@ -306,7 +263,7 @@
 
         // Assert
 
-        verify(pointerInputFilter, never()).onPointerEventMock(any(), any(), any())
+        assertThat(pointerInputFilter.log.getOnPointerEventLog()).hasSize(0)
     }
 
     @Test
@@ -327,9 +284,10 @@
     private fun process_partialTreeHits(numberOfChildrenHit: Int) {
         // Arrange
 
-        val childPointerInputFilter = spy(MockPointerInputFilter())
-        val middlePointerInputFilter = spy(MockPointerInputFilter())
-        val parentPointerInputFilter = spy(MockPointerInputFilter())
+        val log = mutableListOf<LogEntry>()
+        val childPointerInputFilter = PointerInputFilterMock(log)
+        val middlePointerInputFilter = PointerInputFilterMock(log)
+        val parentPointerInputFilter = PointerInputFilterMock(log)
 
         val childLayoutNode =
             LayoutNode(
@@ -373,57 +331,29 @@
 
         // Assert
 
+        val filteredLog = log.getOnPointerEventLog().filter { it.pass == PointerEventPass.Initial }
+
         when (numberOfChildrenHit) {
             3 -> {
-                verify(parentPointerInputFilter).onPointerEventMock(
-                    any(),
-                    eq(PointerEventPass.Initial),
-                    any()
-                )
-                verify(middlePointerInputFilter).onPointerEventMock(
-                    any(),
-                    eq(PointerEventPass.Initial),
-                    any()
-                )
-                verify(childPointerInputFilter).onPointerEventMock(
-                    any(),
-                    eq(PointerEventPass.Initial),
-                    any()
-                )
+                assertThat(filteredLog).hasSize(3)
+                assertThat(filteredLog[0].pointerInputFilter)
+                    .isSameInstanceAs(parentPointerInputFilter)
+                assertThat(filteredLog[1].pointerInputFilter)
+                    .isSameInstanceAs(middlePointerInputFilter)
+                assertThat(filteredLog[2].pointerInputFilter)
+                    .isSameInstanceAs(childPointerInputFilter)
             }
             2 -> {
-                verify(parentPointerInputFilter).onPointerEventMock(
-                    any(),
-                    eq(PointerEventPass.Initial),
-                    any()
-                )
-                verify(middlePointerInputFilter).onPointerEventMock(
-                    any(),
-                    eq(PointerEventPass.Initial),
-                    any()
-                )
-                verify(childPointerInputFilter, never()).onPointerEventMock(
-                    any(),
-                    any(),
-                    any()
-                )
+                assertThat(filteredLog).hasSize(2)
+                assertThat(filteredLog[0].pointerInputFilter)
+                    .isSameInstanceAs(parentPointerInputFilter)
+                assertThat(filteredLog[1].pointerInputFilter)
+                    .isSameInstanceAs(middlePointerInputFilter)
             }
             1 -> {
-                verify(parentPointerInputFilter).onPointerEventMock(
-                    any(),
-                    eq(PointerEventPass.Initial),
-                    any()
-                )
-                verify(middlePointerInputFilter, never()).onPointerEventMock(
-                    any(),
-                    any(),
-                    any()
-                )
-                verify(childPointerInputFilter, never()).onPointerEventMock(
-                    any(),
-                    any(),
-                    any()
-                )
+                assertThat(filteredLog).hasSize(1)
+                assertThat(filteredLog[0].pointerInputFilter)
+                    .isSameInstanceAs(parentPointerInputFilter)
             }
             else -> throw IllegalStateException()
         }
@@ -512,8 +442,12 @@
         val log = pointerInputFilter.log.getOnPointerEventLog()
 
         assertThat(log).hasSize(3)
-        assertThat(log[0].pointerEvent.changes.first()).isEqualTo(expectedInput)
-        assertThat(log[1].pointerEvent.changes.first()).isEqualTo(expectedOutput)
+        PointerInputChangeSubject
+            .assertThat(log[0].pointerEvent.changes.first())
+            .isStructurallyEqualTo(expectedInput)
+        PointerInputChangeSubject
+            .assertThat(log[1].pointerEvent.changes.first())
+            .isStructurallyEqualTo(expectedOutput)
     }
 
     @Test
@@ -571,9 +505,10 @@
 
         // Arrange
 
-        val childPointerInputFilter = spy(MockPointerInputFilter())
-        val middlePointerInputFilter = spy(MockPointerInputFilter())
-        val parentPointerInputFilter = spy(MockPointerInputFilter())
+        val log = mutableListOf<LogEntry>()
+        val childPointerInputFilter = PointerInputFilterMock(log)
+        val middlePointerInputFilter = PointerInputFilterMock(log)
+        val parentPointerInputFilter = PointerInputFilterMock(log)
 
         val childOffset = Offset(cX1.toFloat(), cY1.toFloat())
         val childLayoutNode = LayoutNode(
@@ -610,12 +545,6 @@
 
         val down = PointerInputEvent(0, Uptime.Boot + 7.milliseconds, offset, true)
 
-        val pointerInputHandlers = arrayOf(
-            parentPointerInputFilter,
-            middlePointerInputFilter,
-            childPointerInputFilter
-        )
-
         val expectedPointerInputChanges = arrayOf(
             PointerInputChange(
                 id = PointerId(0),
@@ -673,24 +602,75 @@
 
         // Assert
 
+        val filteredLog = log.getOnPointerEventLog()
+
         // Verify call count
-        pointerInputHandlers.forEach {
-            verify(it, times(PointerEventPass.values().size)).onPointerEventMock(
-                any(),
-                any(),
-                any()
-            )
-        }
+        assertThat(filteredLog).hasSize(PointerEventPass.values().size * 3)
+
         // Verify call values
-        for (pass in PointerEventPass.values()) {
-            for (i in pointerInputHandlers.indices) {
-                verify(pointerInputHandlers[i]).onPointerEventMock(
-                    pointerEventOf(expectedPointerInputChanges[i]),
-                    pass,
-                    expectedSizes[i]
-                )
-            }
-        }
+        filteredLog.verifyOnPointerEventCall(
+            0,
+            parentPointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[0]),
+            PointerEventPass.Initial,
+            expectedSizes[0]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            1,
+            middlePointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[1]),
+            PointerEventPass.Initial,
+            expectedSizes[1]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            2,
+            childPointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[2]),
+            PointerEventPass.Initial,
+            expectedSizes[2]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            3,
+            childPointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[2]),
+            PointerEventPass.Main,
+            expectedSizes[2]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            4,
+            middlePointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[1]),
+            PointerEventPass.Main,
+            expectedSizes[1]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            5,
+            parentPointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[0]),
+            PointerEventPass.Main,
+            expectedSizes[0]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            6,
+            parentPointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[0]),
+            PointerEventPass.Final,
+            expectedSizes[0]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            7,
+            middlePointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[1]),
+            PointerEventPass.Final,
+            expectedSizes[1]
+        )
+        filteredLog.verifyOnPointerEventCall(
+            8,
+            childPointerInputFilter,
+            pointerEventOf(expectedPointerInputChanges[2]),
+            PointerEventPass.Final,
+            expectedSizes[2]
+        )
     }
 
     /**
@@ -716,8 +696,9 @@
 
         // Arrange
 
-        val childPointerInputFilter1 = spy(MockPointerInputFilter())
-        val childPointerInputFilter2 = spy(MockPointerInputFilter())
+        val log = mutableListOf<LogEntry>()
+        val childPointerInputFilter1 = PointerInputFilterMock(log)
+        val childPointerInputFilter2 = PointerInputFilterMock(log)
 
         val childLayoutNode1 =
             LayoutNode(
@@ -787,26 +768,61 @@
         // Assert
 
         // Verify call count
-        verify(childPointerInputFilter1, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(childPointerInputFilter2, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+
+        val child1Log =
+            log.getOnPointerEventLog().filter { it.pointerInputFilter === childPointerInputFilter1 }
+        val child2Log =
+            log.getOnPointerEventLog().filter { it.pointerInputFilter === childPointerInputFilter2 }
+        assertThat(child1Log).hasSize(PointerEventPass.values().size)
+        assertThat(child2Log).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        for (pointerEventPass in PointerEventPass.values()) {
-            verify(childPointerInputFilter1)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange1),
-                    pointerEventPass,
-                    IntSize(50, 50)
-                )
-            verify(childPointerInputFilter2)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange2),
-                    pointerEventPass,
-                    IntSize(50, 50)
-                )
-        }
+
+        val expectedBounds = IntSize(50, 50)
+
+        child1Log.verifyOnPointerEventCall(
+            0,
+            null,
+            pointerEventOf(expectedChange1),
+            PointerEventPass.Initial,
+            expectedBounds
+        )
+        child1Log.verifyOnPointerEventCall(
+            1,
+            null,
+            pointerEventOf(expectedChange1),
+            PointerEventPass.Main,
+            expectedBounds
+        )
+        child1Log.verifyOnPointerEventCall(
+            2,
+            null,
+            pointerEventOf(expectedChange1),
+            PointerEventPass.Final,
+            expectedBounds
+        )
+
+        child2Log.verifyOnPointerEventCall(
+            0,
+            null,
+            pointerEventOf(expectedChange2),
+            PointerEventPass.Initial,
+            expectedBounds
+        )
+        child2Log.verifyOnPointerEventCall(
+            1,
+            null,
+            pointerEventOf(expectedChange2),
+            PointerEventPass.Main,
+            expectedBounds
+        )
+        child2Log.verifyOnPointerEventCall(
+            2,
+            null,
+            pointerEventOf(expectedChange2),
+            PointerEventPass.Final,
+            expectedBounds
+        )
     }
 
     /**
@@ -834,9 +850,10 @@
     @Test
     fun process_3DownOnOverlappingPointerNodes_hitAndDispatchInfoAreCorrect() {
 
-        val childPointerInputFilter1 = spy(MockPointerInputFilter())
-        val childPointerInputFilter2 = spy(MockPointerInputFilter())
-        val childPointerInputFilter3 = spy(MockPointerInputFilter())
+        val log = mutableListOf<LogEntry>()
+        val childPointerInputFilter1 = PointerInputFilterMock(log)
+        val childPointerInputFilter2 = PointerInputFilterMock(log)
+        val childPointerInputFilter3 = PointerInputFilterMock(log)
 
         val childLayoutNode1 = LayoutNode(
             0, 0, 100, 100,
@@ -928,35 +945,85 @@
 
         // Assert
 
-        // Verify call count
-        verify(childPointerInputFilter1, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(childPointerInputFilter2, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(childPointerInputFilter3, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        val child1Log =
+            log.getOnPointerEventLog().filter { it.pointerInputFilter === childPointerInputFilter1 }
+        val child2Log =
+            log.getOnPointerEventLog().filter { it.pointerInputFilter === childPointerInputFilter2 }
+        val child3Log =
+            log.getOnPointerEventLog().filter { it.pointerInputFilter === childPointerInputFilter3 }
+        assertThat(child1Log).hasSize(PointerEventPass.values().size)
+        assertThat(child2Log).hasSize(PointerEventPass.values().size)
+        assertThat(child3Log).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        for (pointerEventPass in PointerEventPass.values()) {
-            verify(childPointerInputFilter1)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange1),
-                    pointerEventPass,
-                    IntSize(100, 100)
-                )
-            verify(childPointerInputFilter2)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange2),
-                    pointerEventPass,
-                    IntSize(100, 100)
-                )
-            verify(childPointerInputFilter3)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange3),
-                    pointerEventPass,
-                    IntSize(100, 100)
-                )
-        }
+
+        val expectedBounds = IntSize(100, 100)
+
+        child1Log.verifyOnPointerEventCall(
+            0,
+            null,
+            pointerEventOf(expectedChange1),
+            PointerEventPass.Initial,
+            expectedBounds
+        )
+        child1Log.verifyOnPointerEventCall(
+            1,
+            null,
+            pointerEventOf(expectedChange1),
+            PointerEventPass.Main,
+            expectedBounds
+        )
+        child1Log.verifyOnPointerEventCall(
+            2,
+            null,
+            pointerEventOf(expectedChange1),
+            PointerEventPass.Final,
+            expectedBounds
+        )
+
+        child2Log.verifyOnPointerEventCall(
+            0,
+            null,
+            pointerEventOf(expectedChange2),
+            PointerEventPass.Initial,
+            expectedBounds
+        )
+        child2Log.verifyOnPointerEventCall(
+            1,
+            null,
+            pointerEventOf(expectedChange2),
+            PointerEventPass.Main,
+            expectedBounds
+        )
+        child2Log.verifyOnPointerEventCall(
+            2,
+            null,
+            pointerEventOf(expectedChange2),
+            PointerEventPass.Final,
+            expectedBounds
+        )
+
+        child3Log.verifyOnPointerEventCall(
+            0,
+            null,
+            pointerEventOf(expectedChange3),
+            PointerEventPass.Initial,
+            expectedBounds
+        )
+        child3Log.verifyOnPointerEventCall(
+            1,
+            null,
+            pointerEventOf(expectedChange3),
+            PointerEventPass.Main,
+            expectedBounds
+        )
+        child3Log.verifyOnPointerEventCall(
+            2,
+            null,
+            pointerEventOf(expectedChange3),
+            PointerEventPass.Final,
+            expectedBounds
+        )
     }
 
     /**
@@ -983,8 +1050,8 @@
     @Test
     fun process_3DownOnFloatingPointerNodeV_hitAndDispatchInfoAreCorrect() {
 
-        val childPointerInputFilter1 = spy(MockPointerInputFilter())
-        val childPointerInputFilter2 = spy(MockPointerInputFilter())
+        val childPointerInputFilter1 = PointerInputFilterMock()
+        val childPointerInputFilter2 = PointerInputFilterMock()
 
         val childLayoutNode1 = LayoutNode(
             0, 0, 100, 150,
@@ -1069,26 +1136,29 @@
 
         // Assert
 
+        val log1 = childPointerInputFilter1.log.getOnPointerEventLog()
+        val log2 = childPointerInputFilter2.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(childPointerInputFilter1, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(childPointerInputFilter2, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log1).hasSize(PointerEventPass.values().size)
+        assertThat(log2).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        for (pointerEventPass in PointerEventPass.values()) {
-            verify(childPointerInputFilter1)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange1, expectedChange3),
-                    pointerEventPass,
-                    IntSize(100, 150)
-                )
-            verify(childPointerInputFilter2)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange2),
-                    pointerEventPass,
-                    IntSize(50, 50)
-                )
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            log1.verifyOnPointerEventCall(
+                index,
+                null,
+                pointerEventOf(expectedChange1, expectedChange3),
+                pass,
+                IntSize(100, 150)
+            )
+            log2.verifyOnPointerEventCall(
+                index,
+                null,
+                pointerEventOf(expectedChange2),
+                pass,
+                IntSize(50, 50)
+            )
         }
     }
 
@@ -1111,8 +1181,9 @@
      */
     @Test
     fun process_3DownOnFloatingPointerNodeH_hitAndDispatchInfoAreCorrect() {
-        val childPointerInputFilter1 = spy(MockPointerInputFilter())
-        val childPointerInputFilter2 = spy(MockPointerInputFilter())
+
+        val childPointerInputFilter1 = PointerInputFilterMock()
+        val childPointerInputFilter2 = PointerInputFilterMock()
 
         val childLayoutNode1 = LayoutNode(
             0, 0, 150, 100,
@@ -1197,26 +1268,29 @@
 
         // Assert
 
+        val log1 = childPointerInputFilter1.log.getOnPointerEventLog()
+        val log2 = childPointerInputFilter2.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(childPointerInputFilter1, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(childPointerInputFilter2, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log1).hasSize(PointerEventPass.values().size)
+        assertThat(log2).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        for (pointerEventPass in PointerEventPass.values()) {
-            verify(childPointerInputFilter1)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange1, expectedChange3),
-                    pointerEventPass,
-                    IntSize(150, 100)
-                )
-            verify(childPointerInputFilter2)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange2),
-                    pointerEventPass,
-                    IntSize(50, 50)
-                )
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            log1.verifyOnPointerEventCall(
+                index,
+                null,
+                pointerEventOf(expectedChange1, expectedChange3),
+                pass,
+                IntSize(150, 100)
+            )
+            log2.verifyOnPointerEventCall(
+                index,
+                null,
+                pointerEventOf(expectedChange2),
+                pass,
+                IntSize(50, 50)
+            )
         }
     }
 
@@ -1247,10 +1321,10 @@
 
         // Arrange
 
-        val pointerInputFilterTopLeft = spy(MockPointerInputFilter())
-        val pointerInputFilterTopRight = spy(MockPointerInputFilter())
-        val pointerInputFilterBottomLeft = spy(MockPointerInputFilter())
-        val pointerInputFilterBottomRight = spy(MockPointerInputFilter())
+        val pointerInputFilterTopLeft = PointerInputFilterMock()
+        val pointerInputFilterTopRight = PointerInputFilterMock()
+        val pointerInputFilterBottomLeft = PointerInputFilterMock()
+        val pointerInputFilterBottomRight = PointerInputFilterMock()
 
         val layoutNodeTopLeft = LayoutNode(
             -1, -1, 1, 1,
@@ -1415,26 +1489,32 @@
             }
 
         // Verify call values
-        PointerEventPass.values().forEach { pointerEventPass ->
-            verify(pointerInputFilterTopLeft).onPointerEventMock(
-                eq(pointerEventOf(*expectedChangesTopLeft.toTypedArray())),
-                eq(pointerEventPass),
-                any()
+
+        val logTopLeft = pointerInputFilterTopLeft.log.getOnPointerEventLog()
+        val logTopRight = pointerInputFilterTopRight.log.getOnPointerEventLog()
+        val logBottomLeft = pointerInputFilterBottomLeft.log.getOnPointerEventLog()
+        val logBottomRight = pointerInputFilterBottomRight.log.getOnPointerEventLog()
+
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            logTopLeft.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(*expectedChangesTopLeft.toTypedArray()),
+                expectedPass = pass
             )
-            verify(pointerInputFilterTopRight).onPointerEventMock(
-                eq(pointerEventOf(*expectedChangesTopRight.toTypedArray())),
-                eq(pointerEventPass),
-                any()
+            logTopRight.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(*expectedChangesTopRight.toTypedArray()),
+                expectedPass = pass
             )
-            verify(pointerInputFilterBottomLeft).onPointerEventMock(
-                eq(pointerEventOf(*expectedChangesBottomLeft.toTypedArray())),
-                eq(pointerEventPass),
-                any()
+            logBottomLeft.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(*expectedChangesBottomLeft.toTypedArray()),
+                expectedPass = pass
             )
-            verify(pointerInputFilterBottomRight).onPointerEventMock(
-                eq(pointerEventOf(*expectedChangesBottomRight.toTypedArray())),
-                eq(pointerEventPass),
-                any()
+            logBottomRight.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(*expectedChangesBottomRight.toTypedArray()),
+                expectedPass = pass
             )
         }
     }
@@ -1463,7 +1543,7 @@
     fun process_rootIsOffset_onlyCorrectPointersHit() {
 
         // Arrange
-        val singlePointerInputFilter = spy(MockPointerInputFilter())
+        val singlePointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0, 0, 2, 2,
             PointerInputModifierImpl2(
@@ -1519,16 +1599,17 @@
                 )
             }
 
+        val log = singlePointerInputFilter.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(singlePointerInputFilter, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        PointerEventPass.values().forEach { pointerEventPass ->
-            verify(singlePointerInputFilter).onPointerEventMock(
-                eq(pointerEventOf(*expectedChanges.toTypedArray())),
-                eq(pointerEventPass),
-                any()
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(*expectedChanges.toTypedArray()),
+                expectedPass = pass
             )
         }
     }
@@ -1536,9 +1617,9 @@
     @Test
     fun process_downOn3NestedPointerInputModifiers_hitAndDispatchInfoAreCorrect() {
 
-        val pointerInputFilter1 = spy(MockPointerInputFilter())
-        val pointerInputFilter2 = spy(MockPointerInputFilter())
-        val pointerInputFilter3 = spy(MockPointerInputFilter())
+        val pointerInputFilter1 = PointerInputFilterMock()
+        val pointerInputFilter2 = PointerInputFilterMock()
+        val pointerInputFilter3 = PointerInputFilterMock()
 
         val modifier = PointerInputModifierImpl2(pointerInputFilter1) then
             PointerInputModifierImpl2(pointerInputFilter2) then
@@ -1584,41 +1665,42 @@
 
         // Assert
 
+        val log1 = pointerInputFilter1.log.getOnPointerEventLog()
+        val log2 = pointerInputFilter2.log.getOnPointerEventLog()
+        val log3 = pointerInputFilter3.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(pointerInputFilter1, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(pointerInputFilter2, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(pointerInputFilter3, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log1).hasSize(PointerEventPass.values().size)
+        assertThat(log2).hasSize(PointerEventPass.values().size)
+        assertThat(log3).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        for (pointerEventPass in PointerEventPass.values()) {
-            verify(pointerInputFilter1)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange),
-                    pointerEventPass,
-                    IntSize(50, 50)
-                )
-            verify(pointerInputFilter2)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange),
-                    pointerEventPass,
-                    IntSize(50, 50)
-                )
-            verify(pointerInputFilter3)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange),
-                    pointerEventPass,
-                    IntSize(50, 50)
-                )
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            log1.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange),
+                expectedPass = pass,
+                expectedBounds = IntSize(50, 50)
+            )
+            log2.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange),
+                expectedPass = pass,
+                expectedBounds = IntSize(50, 50)
+            )
+            log3.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange),
+                expectedPass = pass,
+                expectedBounds = IntSize(50, 50)
+            )
         }
     }
 
     @Test
     fun process_downOnDeeplyNestedPointerInputModifier_hitAndDispatchInfoAreCorrect() {
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
 
         val layoutNode1 =
             LayoutNode(
@@ -1669,28 +1751,29 @@
 
         // Assert
 
+        val log = pointerInputFilter.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(pointerInputFilter, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        for (pointerEventPass in PointerEventPass.values()) {
-            verify(pointerInputFilter)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange),
-                    pointerEventPass,
-                    IntSize(499, 495)
-                )
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange),
+                expectedPass = pass,
+                expectedBounds = IntSize(499, 495)
+            )
         }
     }
 
     @Test
     fun process_downOnComplexPointerAndLayoutNodePath_hitAndDispatchInfoAreCorrect() {
 
-        val pointerInputFilter1 = spy(MockPointerInputFilter())
-        val pointerInputFilter2 = spy(MockPointerInputFilter())
-        val pointerInputFilter3 = spy(MockPointerInputFilter())
-        val pointerInputFilter4 = spy(MockPointerInputFilter())
+        val pointerInputFilter1 = PointerInputFilterMock()
+        val pointerInputFilter2 = PointerInputFilterMock()
+        val pointerInputFilter3 = PointerInputFilterMock()
+        val pointerInputFilter4 = PointerInputFilterMock()
 
         val layoutNode1 = LayoutNode(
             1, 6, 500, 500,
@@ -1769,51 +1852,51 @@
 
         // Assert
 
+        val log1 = pointerInputFilter1.log.getOnPointerEventLog()
+        val log2 = pointerInputFilter2.log.getOnPointerEventLog()
+        val log3 = pointerInputFilter3.log.getOnPointerEventLog()
+        val log4 = pointerInputFilter4.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(pointerInputFilter1, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(pointerInputFilter2, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(pointerInputFilter3, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(pointerInputFilter4, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        PointerEventPass.values()
+        assertThat(log1).hasSize(PointerEventPass.values().size)
+        assertThat(log2).hasSize(PointerEventPass.values().size)
+        assertThat(log3).hasSize(PointerEventPass.values().size)
+        assertThat(log4).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        for (pointerEventPass in PointerEventPass.values()) {
-            verify(pointerInputFilter1)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange1),
-                    pointerEventPass,
-                    IntSize(499, 494)
-                )
-            verify(pointerInputFilter2)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange1),
-                    pointerEventPass,
-                    IntSize(499, 494)
-                )
-            verify(pointerInputFilter3)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange2),
-                    pointerEventPass,
-                    IntSize(497, 492)
-                )
-            verify(pointerInputFilter4)
-                .onPointerEventMock(
-                    pointerEventOf(expectedChange2),
-                    pointerEventPass,
-                    IntSize(497, 492)
-                )
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            log1.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange1),
+                expectedPass = pass,
+                expectedBounds = IntSize(499, 494)
+            )
+            log2.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange1),
+                expectedPass = pass,
+                expectedBounds = IntSize(499, 494)
+            )
+            log3.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange2),
+                expectedPass = pass,
+                expectedBounds = IntSize(497, 492)
+            )
+            log4.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange2),
+                expectedPass = pass,
+                expectedBounds = IntSize(497, 492)
+            )
         }
     }
 
     @Test
     fun process_downOnFullyOverlappingPointerInputModifiers_onlyTopPointerInputModifierReceives() {
 
-        val pointerInputFilter1 = spy(MockPointerInputFilter())
-        val pointerInputFilter2 = spy(MockPointerInputFilter())
+        val pointerInputFilter1 = PointerInputFilterMock()
+        val pointerInputFilter2 = PointerInputFilterMock()
 
         val layoutNode1 = LayoutNode(
             0, 0, 100, 100,
@@ -1842,14 +1925,14 @@
         pointerInputEventProcessor.process(down)
 
         // Assert
-        verify(pointerInputFilter2, times(3)).onPointerEventMock(any(), any(), any())
-        verify(pointerInputFilter1, never()).onPointerEventMock(any(), any(), any())
+        assertThat(pointerInputFilter2.log.getOnPointerEventLog()).hasSize(3)
+        assertThat(pointerInputFilter1.log.getOnPointerEventLog()).hasSize(0)
     }
 
     @Test
     fun process_downOnPointerInputModifierInLayoutNodeWithNoSize_downNotReceived() {
 
-        val pointerInputFilter1 = spy(MockPointerInputFilter())
+        val pointerInputFilter1 = PointerInputFilterMock()
 
         val layoutNode1 = LayoutNode(
             0, 0, 0, 0,
@@ -1868,7 +1951,7 @@
         pointerInputEventProcessor.process(down)
 
         // Assert
-        verify(pointerInputFilter1, never()).onPointerEventMock(any(), any(), any())
+        assertThat(pointerInputFilter1.log.getOnPointerEventLog()).hasSize(0)
     }
 
     // Cancel Handlers
@@ -1883,7 +1966,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
 
         val layoutNode = LayoutNode(
             0, 0, 500, 500,
@@ -1923,21 +2006,20 @@
 
         // Assert
 
+        val log = pointerInputFilter.log.filter { it is OnPointerEventEntry || it is OnCancelEntry }
+
         // Verify call count
-        verify(pointerInputFilter, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log).hasSize(PointerEventPass.values().size + 1)
 
         // Verify call values
-        inOrder(pointerInputFilter) {
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(expectedChange)),
-                    eq(pass),
-                    any()
-                )
-            }
-            verify(pointerInputFilter).onCancel()
+        PointerEventPass.values().forEachIndexed { index, pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange),
+                expectedPass = pass
+            )
         }
+        log.verifyOnCancelCall(PointerEventPass.values().size)
     }
 
     @Test
@@ -1945,7 +2027,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
 
         val layoutNode = LayoutNode(
             0, 0, 500, 500,
@@ -2041,28 +2123,30 @@
 
         // Assert
 
+        val log = pointerInputFilter.log.filter { it is OnPointerEventEntry || it is OnCancelEntry }
+
         // Verify call count
-        verify(pointerInputFilter, times(PointerEventPass.values().size * 2))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log).hasSize(PointerEventPass.values().size * 2 + 1)
 
         // Verify call values
-        inOrder(pointerInputFilter) {
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(*expectedChanges1.toTypedArray())),
-                    eq(pass),
-                    any()
-                )
-            }
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(*expectedChanges2.toTypedArray())),
-                    eq(pass),
-                    any()
-                )
-            }
-            verify(pointerInputFilter).onCancel()
+        var index = 0
+        PointerEventPass.values().forEach { pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(*expectedChanges1.toTypedArray()),
+                expectedPass = pass
+            )
+            index++
         }
+        PointerEventPass.values().forEach { pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(*expectedChanges2.toTypedArray()),
+                expectedPass = pass
+            )
+            index++
+        }
+        log.verifyOnCancelCall(index)
     }
 
     @Test
@@ -2070,13 +2154,13 @@
 
         // Arrange
 
-        val pointerInputFilter1 = spy(MockPointerInputFilter())
+        val pointerInputFilter1 = PointerInputFilterMock()
         val layoutNode1 = LayoutNode(
             0, 0, 199, 199,
             PointerInputModifierImpl2(pointerInputFilter1)
         )
 
-        val pointerInputFilter2 = spy(MockPointerInputFilter())
+        val pointerInputFilter2 = PointerInputFilterMock()
         val layoutNode2 = LayoutNode(
             200, 200, 399, 399,
             PointerInputModifierImpl2(pointerInputFilter2)
@@ -2145,33 +2229,32 @@
 
         // Assert
 
+        val log1 =
+            pointerInputFilter1.log.filter { it is OnPointerEventEntry || it is OnCancelEntry }
+        val log2 =
+            pointerInputFilter2.log.filter { it is OnPointerEventEntry || it is OnCancelEntry }
+
         // Verify call count
-        verify(pointerInputFilter1, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
-        verify(pointerInputFilter2, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log1).hasSize(PointerEventPass.values().size + 1)
+        assertThat(log2).hasSize(PointerEventPass.values().size + 1)
 
         // Verify call values
-        inOrder(pointerInputFilter1) {
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter1).onPointerEventMock(
-                    eq(pointerEventOf(expectedChange1)),
-                    eq(pass),
-                    any()
-                )
-            }
-            verify(pointerInputFilter1).onCancel()
+        var index = 0
+        PointerEventPass.values().forEach { pass ->
+            log1.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange1),
+                expectedPass = pass
+            )
+            log2.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedChange2),
+                expectedPass = pass
+            )
+            index++
         }
-        inOrder(pointerInputFilter2) {
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter2).onPointerEventMock(
-                    eq(pointerEventOf(expectedChange2)),
-                    eq(pass),
-                    any()
-                )
-            }
-            verify(pointerInputFilter2).onCancel()
-        }
+        log1.verifyOnCancelCall(index)
+        log2.verifyOnCancelCall(index)
     }
 
     @Test
@@ -2179,7 +2262,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0, 0, 500, 500,
             PointerInputModifierImpl2(pointerInputFilter)
@@ -2243,28 +2326,30 @@
 
         // Assert
 
+        val log = pointerInputFilter.log.filter { it is OnPointerEventEntry || it is OnCancelEntry }
+
         // Verify call count
-        verify(pointerInputFilter, times(PointerEventPass.values().size * 2))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log).hasSize(PointerEventPass.values().size * 2 + 1)
 
         // Verify call values
-        inOrder(pointerInputFilter) {
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(expectedDown)),
-                    eq(pass),
-                    any()
-                )
-            }
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(expectedMove)),
-                    eq(pass),
-                    any()
-                )
-            }
-            verify(pointerInputFilter).onCancel()
+        var index = 0
+        PointerEventPass.values().forEach { pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedDown),
+                expectedPass = pass
+            )
+            index++
         }
+        PointerEventPass.values().forEach { pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedMove),
+                expectedPass = pass
+            )
+            index++
+        }
+        log.verifyOnCancelCall(index)
     }
 
     @Test
@@ -2272,7 +2357,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0, 0, 500, 500,
             PointerInputModifierImpl2(pointerInputFilter)
@@ -2311,21 +2396,22 @@
 
         // Assert
 
+        val log = pointerInputFilter.log.filter { it is OnPointerEventEntry || it is OnCancelEntry }
+
         // Verify call count
-        verify(pointerInputFilter, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log).hasSize(PointerEventPass.values().size + 1)
 
         // Verify call values
-        inOrder(pointerInputFilter) {
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(expectedDown)),
-                    eq(pass),
-                    any()
-                )
-            }
-            verify(pointerInputFilter).onCancel()
+        var index = 0
+        PointerEventPass.values().forEach { pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedDown),
+                expectedPass = pass
+            )
+            index++
         }
+        log.verifyOnCancelCall(index)
     }
 
     @Test
@@ -2333,7 +2419,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0, 0, 500, 500,
             PointerInputModifierImpl2(
@@ -2399,27 +2485,30 @@
 
         // Assert
 
+        val log = pointerInputFilter.log.filter { it is OnPointerEventEntry || it is OnCancelEntry }
+
         // Verify call count
-        verify(pointerInputFilter, times(PointerEventPass.values().size * 2))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(log).hasSize(PointerEventPass.values().size * 2 + 1)
 
         // Verify call values
-        inOrder(pointerInputFilter) {
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(expectedDown1)),
-                    eq(pass),
-                    any()
-                )
-            }
-            verify(pointerInputFilter).onCancel()
-            for (pass in PointerEventPass.values()) {
-                verify(pointerInputFilter).onPointerEventMock(
-                    eq(pointerEventOf(expectedDown2)),
-                    eq(pass),
-                    any()
-                )
-            }
+        var index = 0
+        PointerEventPass.values().forEach { pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedDown1),
+                expectedPass = pass
+            )
+            index++
+        }
+        log.verifyOnCancelCall(index)
+        index++
+        PointerEventPass.values().forEach { pass ->
+            log.verifyOnPointerEventCall(
+                index = index,
+                expectedEvent = pointerEventOf(expectedDown2),
+                expectedPass = pass
+            )
+            index++
         }
     }
 
@@ -2428,13 +2517,13 @@
 
         // Arrange
 
-        val childPointerInputFilter = spy(MockPointerInputFilter())
+        val childPointerInputFilter = PointerInputFilterMock()
         val childLayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(childPointerInputFilter)
         )
 
-        val parentPointerInputFilter = spy(MockPointerInputFilter())
+        val parentPointerInputFilter = PointerInputFilterMock()
         val parentLayoutNode: LayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(parentPointerInputFilter)
@@ -2489,21 +2578,61 @@
 
         // Assert
 
+        val parentLog = parentPointerInputFilter.log.getOnPointerEventLog()
+        val childLog = childPointerInputFilter.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(parentPointerInputFilter, times(PointerEventPass.values().size * 2))
-            .onPointerEventMock(any(), any(), any())
-        verify(childPointerInputFilter, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(parentLog).hasSize(PointerEventPass.values().size * 2)
+        assertThat(childLog).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        PointerEventPass.values().forEach {
-            verify(parentPointerInputFilter)
-                .onPointerEventMock(eq(pointerEventOf(expectedDownChange)), eq(it), any())
-            verify(childPointerInputFilter)
-                .onPointerEventMock(eq(pointerEventOf(expectedDownChange)), eq(it), any())
-            verify(parentPointerInputFilter)
-                .onPointerEventMock(eq(pointerEventOf(expectedUpChange)), eq(it), any())
-        }
+
+        parentLog.verifyOnPointerEventCall(
+            index = 0,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Initial
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 1,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Main
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 2,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Final
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 3,
+            expectedEvent = pointerEventOf(expectedUpChange),
+            expectedPass = PointerEventPass.Initial
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 4,
+            expectedEvent = pointerEventOf(expectedUpChange),
+            expectedPass = PointerEventPass.Main
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 5,
+            expectedEvent = pointerEventOf(expectedUpChange),
+            expectedPass = PointerEventPass.Final
+        )
+
+        childLog.verifyOnPointerEventCall(
+            index = 0,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Initial
+        )
+        childLog.verifyOnPointerEventCall(
+            index = 1,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Main
+        )
+        childLog.verifyOnPointerEventCall(
+            index = 2,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Final
+        )
     }
 
     @Test
@@ -2511,13 +2640,13 @@
 
         // Arrange
 
-        val childPointerInputFilter = spy(MockPointerInputFilter())
+        val childPointerInputFilter = PointerInputFilterMock()
         val childLayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(childPointerInputFilter)
         )
 
-        val parentPointerInputFilter = spy(MockPointerInputFilter())
+        val parentPointerInputFilter = PointerInputFilterMock()
         val parentLayoutNode: LayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(parentPointerInputFilter)
@@ -2539,8 +2668,8 @@
         pointerInputEventProcessor.process(up)
 
         // Assert
-        verify(childPointerInputFilter).onCancel()
-        verify(parentPointerInputFilter, never()).onCancel()
+        assertThat(childPointerInputFilter.log.getOnCancelLog()).hasSize(1)
+        assertThat(parentPointerInputFilter.log.getOnCancelLog()).hasSize(0)
     }
 
     @Test
@@ -2548,7 +2677,7 @@
 
         // Arrange
 
-        val childPointerInputFilter = spy(MockPointerInputFilter())
+        val childPointerInputFilter = PointerInputFilterMock()
         val childLayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(
@@ -2556,7 +2685,7 @@
             )
         )
 
-        val parentPointerInputFilter = spy(MockPointerInputFilter())
+        val parentPointerInputFilter = PointerInputFilterMock()
         val parentLayoutNode: LayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(
@@ -2613,21 +2742,61 @@
 
         // Assert
 
+        val parentLog = parentPointerInputFilter.log.getOnPointerEventLog()
+        val childLog = childPointerInputFilter.log.getOnPointerEventLog()
+
         // Verify call count
-        verify(parentPointerInputFilter, times(PointerEventPass.values().size * 2))
-            .onPointerEventMock(any(), any(), any())
-        verify(childPointerInputFilter, times(PointerEventPass.values().size))
-            .onPointerEventMock(any(), any(), any())
+        assertThat(parentLog).hasSize(PointerEventPass.values().size * 2)
+        assertThat(childLog).hasSize(PointerEventPass.values().size)
 
         // Verify call values
-        PointerEventPass.values().forEach {
-            verify(parentPointerInputFilter)
-                .onPointerEventMock(eq(pointerEventOf(expectedDownChange)), eq(it), any())
-            verify(childPointerInputFilter)
-                .onPointerEventMock(eq(pointerEventOf(expectedDownChange)), eq(it), any())
-            verify(parentPointerInputFilter)
-                .onPointerEventMock(eq(pointerEventOf(expectedUpChange)), eq(it), any())
-        }
+
+        parentLog.verifyOnPointerEventCall(
+            index = 0,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Initial
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 1,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Main
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 2,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Final
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 3,
+            expectedEvent = pointerEventOf(expectedUpChange),
+            expectedPass = PointerEventPass.Initial
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 4,
+            expectedEvent = pointerEventOf(expectedUpChange),
+            expectedPass = PointerEventPass.Main
+        )
+        parentLog.verifyOnPointerEventCall(
+            index = 5,
+            expectedEvent = pointerEventOf(expectedUpChange),
+            expectedPass = PointerEventPass.Final
+        )
+
+        childLog.verifyOnPointerEventCall(
+            index = 0,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Initial
+        )
+        childLog.verifyOnPointerEventCall(
+            index = 1,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Main
+        )
+        childLog.verifyOnPointerEventCall(
+            index = 2,
+            expectedEvent = pointerEventOf(expectedDownChange),
+            expectedPass = PointerEventPass.Final
+        )
     }
 
     @Test
@@ -2635,13 +2804,13 @@
 
         // Arrange
 
-        val childPointerInputFilter = spy(MockPointerInputFilter())
+        val childPointerInputFilter = PointerInputFilterMock()
         val childLayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(childPointerInputFilter)
         )
 
-        val parentPointerInputFilter = spy(MockPointerInputFilter())
+        val parentPointerInputFilter = PointerInputFilterMock()
         val parentLayoutNode: LayoutNode = LayoutNode(
             0, 0, 100, 100,
             PointerInputModifierImpl2(parentPointerInputFilter)
@@ -2663,8 +2832,8 @@
         pointerInputEventProcessor.process(up)
 
         // Assert
-        verify(childPointerInputFilter).onCancel()
-        verify(parentPointerInputFilter, never()).onCancel()
+        assertThat(childPointerInputFilter.log.getOnCancelLog()).hasSize(1)
+        assertThat(parentPointerInputFilter.log.getOnCancelLog()).hasSize(0)
     }
 
     @Test
@@ -2687,7 +2856,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
 
         val layoutNode = LayoutNode(
             0, 0, 1, 1,
@@ -2734,7 +2903,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0, 0, 1, 1,
             PointerInputModifierImpl2(
@@ -2764,7 +2933,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0, 0, 1, 1,
             PointerInputModifierImpl2(
@@ -2796,7 +2965,7 @@
 
         // Arrange
 
-        val pointerInputFilter = spy(MockPointerInputFilter())
+        val pointerInputFilter = PointerInputFilterMock()
         val layoutNode = LayoutNode(
             0, 0, 1, 1,
             PointerInputModifierImpl2(
@@ -3044,23 +3213,31 @@
     }
 }
 
-open class MockPointerInputFilter : PointerInputFilter() {
-
-    override fun onPointerEvent(
-        pointerEvent: PointerEvent,
-        pass: PointerEventPass,
-        bounds: IntSize
-    ) {
-        onPointerEventMock(pointerEvent, pass, bounds as Any)
+private fun List<LogEntry>.verifyOnPointerEventCall(
+    index: Int,
+    expectedPif: PointerInputFilter? = null,
+    expectedEvent: PointerEvent,
+    expectedPass: PointerEventPass,
+    expectedBounds: IntSize? = null
+) {
+    val logEntry = this[index]
+    assertThat(logEntry).isInstanceOf(OnPointerEventEntry::class.java)
+    val entry = logEntry as OnPointerEventEntry
+    if (expectedPif != null) {
+        assertThat(entry.pointerInputFilter).isSameInstanceAs(expectedPif)
     }
-
-    override fun onCancel() {}
-
-    open fun onPointerEventMock(
-        pointerEvent: PointerEvent,
-        pass: PointerEventPass,
-        bounds: Any
-    ) {
-        pointerEvent.changes
+    PointerEventSubject
+        .assertThat(entry.pointerEvent)
+        .isStructurallyEqualTo(expectedEvent)
+    assertThat(entry.pass).isEqualTo(expectedPass)
+    if (expectedBounds != null) {
+        assertThat(entry.bounds).isEqualTo(expectedBounds)
     }
+}
+
+private fun List<LogEntry>.verifyOnCancelCall(
+    index: Int
+) {
+    val logEntry = this[index]
+    assertThat(logEntry).isInstanceOf(OnCancelEntry::class.java)
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
index eb19be8..57c247c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
@@ -715,7 +715,9 @@
                 arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        val moveConsumed = down.moveTo(7.milliseconds, 8f, 9f).consume(1f, 0f)
+        val moveConsumed =
+            down.moveTo(7.milliseconds, 8f, 9f)
+                .apply { consumePositionChange(1f, 0f) }
         val expected =
             MotionEvent(
                 7,
@@ -772,7 +774,8 @@
             )
 
         val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
-        val bMoveConsumed = bDown.moveTo(15.milliseconds, 18f, 19f).consume(1f, 0f)
+        val bMoveConsumed =
+            bDown.moveTo(15.milliseconds, 18f, 19f).apply { consumePositionChange(1f, 0f) }
 
         val expected =
             MotionEvent(
@@ -1023,7 +1026,8 @@
                 arrayOf(PointerCoords(3f, 4f))
             )
 
-        val move1Consumed = down.moveTo(5.milliseconds, 6f, 7f).consume(0f, 1f)
+        val move1Consumed =
+            down.moveTo(5.milliseconds, 6f, 7f).apply { consumePositionChange(0f, 1f) }
         val motionEvent2 =
             MotionEvent(
                 5,
@@ -2005,7 +2009,6 @@
     @Test
     fun onPointerEvent_1PointerDownViewRetsFalse_nothingConsumed() {
         val change = down(1, 2.milliseconds, 3f, 4f)
-        val expected = change.deepCopy()
         val motionEvent1 =
             MotionEvent(
                 2,
@@ -2021,13 +2024,12 @@
             pointerEventOf(change, motionEvent = motionEvent1)
         )
 
-        assertThat(change).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(change).nothingConsumed()
     }
 
     @Test
     fun onPointerEvent_1PointerDownViewRetsTrue_everythingConsumed() {
         val change = down(1, 2.milliseconds, 3f, 4f)
-        val expected = change.deepCopy().apply { consumeDownChange() }
         val motionEvent1 =
             MotionEvent(
                 2,
@@ -2044,7 +2046,7 @@
             pointerEventOf(change, motionEvent = motionEvent1)
         )
 
-        assertThat(change).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(change).downConsumed()
     }
 
     @Test
@@ -2073,14 +2075,14 @@
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(down, motionEvent = motionEvent1)
         )
-        val upExpected = upActual.deepCopy().apply { consumeAllChanges() }
 
         retVal = false
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(upActual, motionEvent = motionEvent2)
         )
 
-        assertThat(upActual).isEqualTo(upExpected)
+        PointerInputChangeSubject.assertThat(upActual).downConsumed()
+        PointerInputChangeSubject.assertThat(upActual).positionChangeNotConsumed()
     }
 
     @Test
@@ -2096,7 +2098,6 @@
                 arrayOf(PointerCoords(3f, 4f))
             )
         val upActual = down.up(5.milliseconds)
-        val expected = upActual.deepCopy().apply { consumeDownChange() }
         val motionEvent2 =
             MotionEvent(
                 5,
@@ -2116,7 +2117,7 @@
             pointerEventOf(upActual, motionEvent = motionEvent2)
         )
 
-        assertThat(upActual).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(upActual).downConsumed()
     }
 
     @Test
@@ -2149,11 +2150,6 @@
 
         retVal = true
 
-        val expected = listOf(
-            aMove.deepCopy().apply { consumeAllChanges() },
-            bDown.deepCopy().apply { consumeAllChanges() }
-        )
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -2168,7 +2164,9 @@
 
         // Assert
 
-        assertThat(listOf(aMove, bDown)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bDown).downConsumed()
+        PointerInputChangeSubject.assertThat(bDown).positionChangeNotConsumed()
     }
 
     @Test
@@ -2201,11 +2199,6 @@
 
         retVal = true
 
-        val expected = listOf(
-            aMove.deepCopy().apply { consumeDownChange() },
-            bDown.deepCopy().apply { consumeDownChange() }
-        )
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -2218,7 +2211,9 @@
 
         // Assert
 
-        assertThat(listOf(aMove, bDown)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bDown).downConsumed()
+        PointerInputChangeSubject.assertThat(bDown).positionChangeNotConsumed()
     }
 
     @Test
@@ -2263,11 +2258,6 @@
 
         retVal = true
 
-        val expected = listOf(
-            aMove2.deepCopy().apply { consumeAllChanges() },
-            bUp.deepCopy().apply { consumeAllChanges() }
-        )
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -2284,7 +2274,9 @@
 
         // Assert
 
-        assertThat(listOf(aMove2, bUp)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bUp).downConsumed()
+        PointerInputChangeSubject.assertThat(bUp).positionChangeNotConsumed()
     }
 
     @Test
@@ -2329,11 +2321,6 @@
 
         retVal = true
 
-        val expected = listOf(
-            aMove2.deepCopy().apply { consumeDownChange() },
-            bUp.deepCopy().apply { consumeDownChange() }
-        )
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -2349,7 +2336,9 @@
 
         // Assert
 
-        assertThat(listOf(aMove2, bUp)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bUp).downConsumed()
+        PointerInputChangeSubject.assertThat(bUp).positionChangeNotConsumed()
     }
 
     @Test
@@ -2365,7 +2354,6 @@
                 arrayOf(PointerCoords(3f, 4f))
             )
         val move = down.moveTo(7.milliseconds, 8f, 9f)
-        val expected = move.deepCopy().apply { consumeAllChanges() }
         val motionEvent2 =
             MotionEvent(
                 7,
@@ -2385,7 +2373,8 @@
             pointerEventOf(move, motionEvent = motionEvent2)
         )
 
-        assertThat(move).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(move).downNotConsumed()
+        PointerInputChangeSubject.assertThat(move).positionChangeConsumed(Offset(5f, 5f))
     }
 
     @Test
@@ -2411,7 +2400,6 @@
                 arrayOf(PointerCoords(8f, 9f))
             )
         retVal = true
-        val expected = move.deepCopy().apply { consumeAllChanges() }
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(down, motionEvent = motionEvent1)
         )
@@ -2420,7 +2408,8 @@
             pointerEventOf(move, motionEvent = motionEvent2)
         )
 
-        assertThat(move).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(move).downNotConsumed()
+        PointerInputChangeSubject.assertThat(move).positionChangeConsumed(Offset(5f, 5f))
     }
 
     @Test
@@ -2477,12 +2466,6 @@
 
         retVal = true
 
-        val expected =
-            listOf(
-                aMove2.deepCopy().apply { consumeAllChanges() },
-                bMove1.deepCopy().apply { consumeAllChanges() }
-            )
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -2499,7 +2482,10 @@
 
         // Assert
 
-        assertThat(listOf(aMove2, bMove1)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).downNotConsumed()
+        PointerInputChangeSubject.assertThat(aMove2).positionChangeConsumed(Offset(8f, 9f))
+        PointerInputChangeSubject.assertThat(bMove1).downNotConsumed()
+        PointerInputChangeSubject.assertThat(bMove1).positionChangeConsumed(Offset(18f, 19f))
     }
 
     @Test
@@ -2556,11 +2542,6 @@
 
         retVal = true
 
-        val expected = listOf(
-            aMove2.deepCopy().apply { consumeAllChanges() },
-            bMove1.deepCopy().apply { consumeAllChanges() }
-        )
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -2576,7 +2557,10 @@
 
         // Assert
 
-        assertThat(listOf(aMove2, bMove1)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).downNotConsumed()
+        PointerInputChangeSubject.assertThat(aMove2).positionChangeConsumed(Offset(8f, 9f))
+        PointerInputChangeSubject.assertThat(bMove1).downNotConsumed()
+        PointerInputChangeSubject.assertThat(bMove1).positionChangeConsumed(Offset(18f, 19f))
     }
 
     // Verification of no further consumption after initial consumption (because if something was
@@ -2596,7 +2580,6 @@
                 arrayOf(PointerCoords(3f, 4f))
             )
         val aMove = aDownConsumed.moveTo(5.milliseconds, 6f, 7f)
-        val expected = aMove.deepCopy()
         val motionEvent2 =
             MotionEvent(
                 5,
@@ -2615,7 +2598,7 @@
             pointerEventOf(aMove, motionEvent = motionEvent2)
         )
 
-        assertThat(aMove).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove).nothingConsumed()
     }
 
     @Test
@@ -2646,7 +2629,6 @@
                 )
             )
         retVal = true
-        val expected = aUp.deepCopy()
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
@@ -2655,7 +2637,7 @@
             pointerEventOf(aUp, motionEvent = motionEvent2)
         )
 
-        assertThat(aUp).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aUp).nothingConsumed()
     }
 
     @Test
@@ -2682,8 +2664,6 @@
                 arrayOf(PointerCoords(3f, 4f), PointerCoords(13f, 14f))
             )
 
-        val expected = listOf(aMove1.deepCopy(), bDown.deepCopy())
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
         )
@@ -2691,7 +2671,8 @@
             pointerEventOf(aMove1, bDown, motionEvent = motionEvent2)
         )
 
-        assertThat(listOf(aMove1, bDown)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove1).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bDown).nothingConsumed()
     }
 
     @Test
@@ -2728,7 +2709,6 @@
                 arrayOf(PointerProperties(0), PointerProperties(1)),
                 arrayOf(PointerCoords(6f, 7f), PointerCoords(22f, 23f))
             )
-        val expected = listOf(aMove2.deepCopy(), bMove.deepCopy())
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
@@ -2740,7 +2720,8 @@
             pointerEventOf(aMove2, bMove, motionEvent = motionEvent3)
         )
 
-        assertThat(listOf(aMove2, bMove)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bMove).nothingConsumed()
     }
 
     @Test
@@ -2778,7 +2759,9 @@
             )
 
         val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
-        val bMoveConsumed = bDown.moveTo(15.milliseconds, 18f, 19f).consume(1f, 0f)
+        val bMoveConsumed =
+            bDown.moveTo(15.milliseconds, 18f, 19f)
+                .apply { consumePositionChange(1f, 0f) }
         val motionEvent3 =
             MotionEvent(
                 7,
@@ -2795,8 +2778,6 @@
                 )
             )
 
-        val expected = listOf(aMove2.deepCopy(), bMoveConsumed.deepCopy())
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -2810,8 +2791,9 @@
         )
 
         // Assert
-
-        assertThat(listOf(aMove2, bMoveConsumed)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bMoveConsumed).downNotConsumed()
+        PointerInputChangeSubject.assertThat(bMoveConsumed).positionChangeConsumed(Offset(1f, 0f))
     }
 
     @Test
@@ -2853,8 +2835,6 @@
                 arrayOf(PointerCoords(31f, 32f), PointerCoords(33f, 34f))
             )
 
-        val expected = listOf(aMove2.deepCopy(), bMove.deepCopy())
-
         // Act
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
@@ -2867,7 +2847,8 @@
         )
 
         // Assert
-        assertThat(listOf(aMove2, bMove)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bMove).nothingConsumed()
     }
 
     // Verifies resetting of consumption.
@@ -2908,7 +2889,6 @@
                 arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(13f, 14f))
             )
-        val expected = bDown.deepCopy().apply { consumeDownChange() }
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDownConsumed, motionEvent = motionEvent1)
@@ -2920,7 +2900,8 @@
             pointerEventOf(bDown, motionEvent = motionEvent3)
         )
 
-        assertThat(bDown).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(bDown).downConsumed()
+        PointerInputChangeSubject.assertThat(bDown).positionChangeNotConsumed()
     }
 
     @Test
@@ -2985,8 +2966,6 @@
                 arrayOf(PointerCoords(53f, 54f))
             )
 
-        val expected = cDown.deepCopy().apply { consumeDownChange() }
-
         // Act
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
@@ -3007,7 +2986,8 @@
         )
 
         // Assert
-        assertThat(cDown).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(cDown).downConsumed()
+        PointerInputChangeSubject.assertThat(cDown).positionChangeNotConsumed()
     }
 
     // Verification of consumption when the view rets false and then is set to return true.
@@ -3035,7 +3015,6 @@
                 arrayOf(PointerCoords(6f, 7f))
             )
         retVal = false
-        val expected = aMove.deepCopy()
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
@@ -3045,7 +3024,7 @@
             pointerEventOf(aMove, motionEvent = motionEvent2)
         )
 
-        assertThat(aMove).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove).nothingConsumed()
     }
 
     @Test
@@ -3071,7 +3050,6 @@
                 arrayOf(PointerCoords(3f, 4f))
             )
         retVal = false
-        val expected = aUp.deepCopy()
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
@@ -3081,7 +3059,7 @@
             pointerEventOf(aUp, motionEvent = motionEvent2)
         )
 
-        assertThat(aUp).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aUp).nothingConsumed()
     }
 
     @Test
@@ -3114,8 +3092,6 @@
 
         retVal = false
 
-        val expected = listOf(aMove1.deepCopy(), bDown.deepCopy())
-
         // Act
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
@@ -3128,7 +3104,8 @@
 
         // Assert
 
-        assertThat(listOf(aMove1, bDown)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove1).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bDown).nothingConsumed()
     }
 
     @Test
@@ -3173,8 +3150,6 @@
 
         retVal = false
 
-        val expected = listOf(aMove2.deepCopy(), bMove1.deepCopy())
-
         // Act
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
@@ -3190,7 +3165,8 @@
 
         // Assert
 
-        assertThat(listOf(aMove2, bMove1)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bMove1).nothingConsumed()
     }
 
     @Test
@@ -3235,8 +3211,6 @@
 
         retVal = false
 
-        val expected = listOf(aMove2.deepCopy(), bUp.deepCopy())
-
         // Act
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
@@ -3252,7 +3226,8 @@
 
         // Assert
 
-        assertThat(listOf(aMove2, bUp)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bUp).nothingConsumed()
     }
 
     @Test
@@ -3306,8 +3281,6 @@
 
         retVal = false
 
-        val expected = listOf(bMove2.deepCopy(), cDown.deepCopy())
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -3322,7 +3295,8 @@
             pointerEventOf(bMove2, cDown, motionEvent = motionEvent4)
         )
 
-        assertThat(listOf(bMove2, cDown)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(bMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(cDown).nothingConsumed()
     }
 
     // Verification of correct passes being used
@@ -3836,7 +3810,9 @@
                 arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        val moveConsumed = down.moveTo(7.milliseconds, 8f, 9f).consume(1f, 0f)
+        val moveConsumed =
+            down.moveTo(7.milliseconds, 8f, 9f)
+                .apply { consumePositionChange(1f, 0f) }
         val motionEvent2 =
             MotionEvent(
                 7,
@@ -3895,7 +3871,8 @@
             )
 
         val aMove2 = aMove1.moveTo(15.milliseconds, 8f, 9f)
-        val bMoveConsumed = bDown.moveTo(15.milliseconds, 18f, 19f).consume(1f, 0f)
+        val bMoveConsumed =
+            bDown.moveTo(15.milliseconds, 18f, 19f).apply { consumePositionChange(1f, 0f) }
         val motionEvent3 =
             MotionEvent(
                 7,
@@ -3949,14 +3926,14 @@
                 arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        val expected = down.deepCopy().apply { consumeDownChange() }
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPass(
             pointerEventOf(down, motionEvent = motionEvent1),
             PointerEventPass.Initial
         )
 
-        assertThat(down).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(down).downConsumed()
+        PointerInputChangeSubject.assertThat(down).positionChangeNotConsumed()
     }
 
     @Test
@@ -3981,7 +3958,6 @@
                 arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(3f, 4f))
             )
-        val expected = up.deepCopy().apply { consumeDownChange() }
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(down, motionEvent = motionEvent1)
         )
@@ -3991,7 +3967,8 @@
             PointerEventPass.Initial
         )
 
-        assertThat(up).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(up).downConsumed()
+        PointerInputChangeSubject.assertThat(up).positionChangeNotConsumed()
     }
 
     @Test
@@ -4022,8 +3999,6 @@
                 arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
             )
 
-        val expected = listOf(aMove, bDown).map { it.deepCopy().apply { consumeDownChange() } }
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -4037,7 +4012,9 @@
 
         // Assert
 
-        assertThat(listOf(aMove, bDown)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bDown).downConsumed()
+        PointerInputChangeSubject.assertThat(bDown).positionChangeNotConsumed()
     }
 
     @Test
@@ -4080,8 +4057,6 @@
                 arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f))
             )
 
-        val expected = listOf(aMove2, bUp).map { it.deepCopy().apply { consumeDownChange() } }
-
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(aDown, motionEvent = motionEvent1)
         )
@@ -4098,7 +4073,9 @@
 
         // Assert
 
-        assertThat(listOf(aMove2, bUp)).isEqualTo(expected)
+        PointerInputChangeSubject.assertThat(aMove2).nothingConsumed()
+        PointerInputChangeSubject.assertThat(bUp).downConsumed()
+        PointerInputChangeSubject.assertThat(bUp).positionChangeNotConsumed()
     }
 
     @Test
@@ -4123,8 +4100,6 @@
                 arrayOf(PointerProperties(0)),
                 arrayOf(PointerCoords(8f, 9f))
             )
-        val expected1 = move.deepCopy()
-        val expected2 = move.deepCopy().apply { consumeAllChanges() }
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverAllPasses(
             pointerEventOf(down, motionEvent = motionEvent1)
@@ -4136,14 +4111,15 @@
             PointerEventPass.Main
         )
 
-        assertThat(move).isEqualTo(expected1)
+        PointerInputChangeSubject.assertThat(move).nothingConsumed()
 
         pointerInteropFilter.pointerInputFilter::onPointerEvent.invokeOverPass(
             pointerEventOf(move, motionEvent = motionEvent2),
             PointerEventPass.Final
         )
 
-        assertThat(move).isEqualTo(expected2)
+        PointerInputChangeSubject.assertThat(move).downNotConsumed()
+        PointerInputChangeSubject.assertThat(move).positionChangeConsumed(Offset(5f, 5f))
     }
 
     @Test
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
index 77b5259..96404d3 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Uptime
 import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.map
@@ -126,47 +127,56 @@
         val bounds = IntSize(50, 50)
         val emitter1 = PointerInputChangeEmitter(0)
         val emitter2 = PointerInputChangeEmitter(1)
-        val expectedChanges = listOf(
-            listOf(
-                emitter1.nextChange(Offset(5f, 5f)),
-                emitter2.nextChange(Offset(10f, 10f))
+        val expectedEvents = listOf(
+            PointerEvent(
+                listOf(
+                    emitter1.nextChange(Offset(5f, 5f)),
+                    emitter2.nextChange(Offset(10f, 10f))
+                )
             ),
-            listOf(
-                emitter1.nextChange(Offset(6f, 6f)),
-                emitter2.nextChange(Offset(10f, 10f), down = false)
+            PointerEvent(
+                listOf(
+                    emitter1.nextChange(Offset(6f, 6f)),
+                    emitter2.nextChange(Offset(10f, 10f), down = false)
+                )
             ),
             // Synthetic cancel should look like this;
             // only one pointer since the previous event's second pointer changed to up,
             // the old position unchanged, 'down' changed from true to false, and the downChange
             // marked as consumed.
-            listOf(
-                PointerInputChange(
-                    PointerId(0),
-                    current = PointerInputData(
-                        position = Offset(6f, 6f),
-                        down = false
-                    ),
-                    previous = PointerInputData(
-                        position = Offset(6f, 6f),
-                        down = true
-                    ),
-                    consumed = ConsumedData(downChange = true)
+            PointerEvent(
+                listOf(
+                    PointerInputChange(
+                        PointerId(0),
+                        current = PointerInputData(
+                            position = Offset(6f, 6f),
+                            down = false
+                        ),
+                        previous = PointerInputData(
+                            position = Offset(6f, 6f),
+                            down = true
+                        ),
+                        consumed = ConsumedData(downChange = true)
+                    )
                 )
             )
         )
 
-        expectedChanges.take(expectedChanges.size - 1).forEach {
-            filter.onPointerEvent(PointerEvent(changes = it), PointerEventPass.Main, bounds)
+        expectedEvents.take(expectedEvents.size - 1).forEach {
+            filter.onPointerEvent(it, PointerEventPass.Main, bounds)
         }
         filter.onCancel()
 
         val received = withTimeout(200) {
-            results.receiveAsFlow()
-                .map { it.changes }
-                .toList()
+            results.receiveAsFlow().toList()
         }
 
-        assertEquals(expectedChanges, received)
+        assertThat(expectedEvents).hasSize(received.size)
+
+        expectedEvents.forEachIndexed { index, expectedEvent ->
+            val actualEvent = received[index]
+            PointerEventSubject.assertThat(actualEvent).isStructurallyEqualTo(expectedEvent)
+        }
 
         reader.cancel()
     }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt
index 2413b22..e22a920 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt
@@ -26,6 +26,9 @@
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Uptime
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
 
 internal fun PointerInputEventData(
     id: Int,
@@ -193,14 +196,6 @@
         motionEvent = motionEvent
     )
 
-internal fun PointerInputChange.deepCopy() =
-    PointerInputChange(
-        id,
-        current.copy(),
-        previous.copy(),
-        consumed.copy()
-    )
-
 internal fun pointerEventOf(
     vararg changes: PointerInputChange,
     motionEvent: MotionEvent = MotionEventDouble
@@ -286,4 +281,98 @@
 ) : LogEntry()
 
 internal fun internalPointerEventOf(vararg changes: PointerInputChange) =
-    InternalPointerEvent(changes.toList().associateBy { it.id }.toMutableMap(), MotionEventDouble)
\ No newline at end of file
+    InternalPointerEvent(changes.toList().associateBy { it.id }.toMutableMap(), MotionEventDouble)
+
+internal class PointerEventSubject(
+    metaData: FailureMetadata,
+    val actual: PointerEvent
+) : Subject(metaData, actual) {
+    companion object {
+        private val Factory =
+            Factory<PointerEventSubject, PointerEvent> { metadata, actual ->
+                PointerEventSubject(metadata, actual)
+            }
+
+        fun assertThat(actual: PointerEvent): PointerEventSubject {
+            return Truth.assertAbout(Factory).that(actual)
+        }
+    }
+
+    fun isStructurallyEqualTo(expected: PointerEvent) {
+        check("motionEvent").that(actual.motionEvent).isEqualTo(expected.motionEvent)
+        val actualChanges = actual.changes
+        val expectedChanges = expected.changes
+        check("changes.size").that(actualChanges.size).isEqualTo(expectedChanges.size)
+        actualChanges.forEachIndexed { i, _ ->
+            check("id").that(actualChanges[i].id).isEqualTo(expectedChanges[i].id)
+            check("current").that(actualChanges[i].current).isEqualTo(expectedChanges[i].current)
+            check("previous").that(actualChanges[i].previous).isEqualTo(expectedChanges[i].previous)
+            check("consumed.downChange")
+                .that(actualChanges[i].consumed.downChange)
+                .isEqualTo(expectedChanges[i].consumed.downChange)
+            check("consumed.positionChange")
+                .that(actualChanges[i].consumed.positionChange)
+                .isEqualTo(expectedChanges[i].consumed.positionChange)
+        }
+    }
+}
+
+internal class PointerInputChangeSubject(
+    metaData: FailureMetadata,
+    val actual: PointerInputChange
+) : Subject(metaData, actual) {
+
+    companion object {
+
+        private val Factory =
+            Factory<PointerInputChangeSubject, PointerInputChange> { metadata, actual ->
+                PointerInputChangeSubject(metadata, actual)
+            }
+
+        fun assertThat(actual: PointerInputChange?): PointerInputChangeSubject {
+            return Truth.assertAbout(Factory).that(actual)
+        }
+    }
+
+    fun nothingConsumed() {
+        check("consumed.downChange").that(actual.consumed.downChange).isEqualTo(false)
+        check("consumed.positionChange").that(actual.consumed.positionChange).isEqualTo(Offset.Zero)
+    }
+
+    fun downConsumed() {
+        check("consumed.downChange").that(actual.consumed.downChange).isEqualTo(true)
+    }
+
+    fun downNotConsumed() {
+        check("consumed.downChange").that(actual.consumed.downChange).isEqualTo(false)
+    }
+
+    fun positionChangeConsumed(expected: Offset) {
+        check("consumed.positionChangeConsumed")
+            .that(actual.consumed.positionChange).isEqualTo(expected)
+    }
+
+    fun positionChangeNotConsumed() {
+        positionChangeConsumed(Offset.Zero)
+    }
+
+    fun isStructurallyEqualTo(expected: PointerInputChange) {
+        check("id").that(actual.id).isEqualTo(expected.id)
+        check("current").that(actual.current).isEqualTo(expected.current)
+        check("previous").that(actual.previous).isEqualTo(expected.previous)
+        check("consumed.downChange")
+            .that(actual.consumed.downChange)
+            .isEqualTo(expected.consumed.downChange)
+        check("consumed.positionChange")
+            .that(actual.consumed.positionChange)
+            .isEqualTo(expected.consumed.positionChange)
+    }
+}
+
+internal fun PointerInputChange.deepCopy() =
+    PointerInputChange(
+        id,
+        current.copy(),
+        previous.copy(),
+        ConsumedData(consumed.positionChange, consumed.downChange)
+    )
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt
index 7f4fff1..e5d91b1 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.selection.SimpleContainer
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
 import androidx.ui.test.createAndroidComposeRule
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupAlignmentTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupAlignmentTest.kt
index 85ea494..3112086 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupAlignmentTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupAlignmentTest.kt
@@ -26,7 +26,6 @@
 import androidx.compose.ui.onGloballyPositioned
 import androidx.compose.ui.platform.LayoutDirectionAmbient
 import androidx.compose.ui.platform.ViewAmbient
-import androidx.compose.ui.selection.SimpleContainer
 import androidx.compose.ui.unit.IntBounds
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
index f970558..8bd6acd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
@@ -27,7 +27,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.Owner
-import androidx.compose.ui.selection.SimpleContainer
 import androidx.compose.ui.unit.dp
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.assertion.ViewAssertions
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
index d1fecd3..afcac0f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.onGloballyPositioned
-import androidx.compose.ui.selection.SimpleContainer
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTestUtils.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTestUtils.kt
index f0c2cd2..7e8479f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTestUtils.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTestUtils.kt
@@ -20,7 +20,18 @@
 import android.view.View
 import android.view.WindowManager
 import androidx.activity.ComponentActivity
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Layout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.Placeable
 import androidx.compose.ui.node.Owner
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.enforce
+import androidx.compose.ui.unit.hasFixedHeight
+import androidx.compose.ui.unit.hasFixedWidth
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.Root
 import androidx.test.espresso.assertion.ViewAssertions
@@ -29,6 +40,7 @@
 import org.hamcrest.Description
 import org.hamcrest.Matcher
 import org.hamcrest.TypeSafeMatcher
+import kotlin.math.max
 
 // TODO(b/139861182): Remove all of this and provide helpers on rule
 internal fun ComposeTestRule.popupMatches(popupTestTag: String, viewMatcher: Matcher<in View>) {
@@ -66,4 +78,61 @@
             WindowManager.LayoutParams.FLAG_SECURE
         )
     }
-}
\ No newline at end of file
+}
+
+/**
+ * A Container Box implementation used for selection children and handle layout
+ */
+@Composable
+internal fun SimpleContainer(
+    modifier: Modifier = Modifier,
+    width: Dp? = null,
+    height: Dp? = null,
+    children: @Composable () -> Unit
+) {
+    Layout(children, modifier) { measurables, incomingConstraints ->
+        val containerConstraints = Constraints()
+            .copy(
+                width?.toIntPx() ?: 0,
+                width?.toIntPx() ?: Constraints.Infinity,
+                height?.toIntPx() ?: 0,
+                height?.toIntPx() ?: Constraints.Infinity
+            )
+            .enforce(incomingConstraints)
+        val childConstraints = containerConstraints.copy(minWidth = 0, minHeight = 0)
+        var placeable: Placeable? = null
+        val containerWidth = if (
+            containerConstraints.hasFixedWidth
+        ) {
+            containerConstraints.maxWidth
+        } else {
+            placeable = measurables.firstOrNull()?.measure(childConstraints)
+            max((placeable?.width ?: 0), containerConstraints.minWidth)
+        }
+        val containerHeight = if (
+            containerConstraints.hasFixedHeight
+        ) {
+            containerConstraints.maxHeight
+        } else {
+            if (placeable == null) {
+                placeable = measurables.firstOrNull()?.measure(childConstraints)
+            }
+            max((placeable?.height ?: 0), containerConstraints.minHeight)
+        }
+        layout(containerWidth, containerHeight) {
+            val p = placeable ?: measurables.firstOrNull()?.measure(childConstraints)
+            p?.let {
+                val position = Alignment.Center.align(
+                    IntSize(
+                        containerWidth - it.width,
+                        containerHeight - it.height
+                    )
+                )
+                it.placeRelative(
+                    position.x,
+                    position.y
+                )
+            }
+        }
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
index 2072359..c741ab1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
@@ -189,7 +189,7 @@
  * @param positionChange The amount of change to the position that has been consumed.
  * @param downChange True if a change to down or up has been consumed.
  */
-data class ConsumedData(
+class ConsumedData(
     var positionChange: Offset = Offset.Companion.Zero,
     var downChange: Boolean = false
 )
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt
index 63e352e..91ac6d7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt
@@ -73,21 +73,6 @@
         consumed = ConsumedData()
     )
 
-internal fun PointerInputChange.consume(
-    dx: Float = 0f,
-    dy: Float = 0f,
-    downChange: Boolean = false
-) =
-    copy(
-        consumed = consumed.copy(
-            positionChange = Offset(
-                consumed.positionChange.x + dx,
-                consumed.positionChange.y + dy
-            ),
-            downChange = consumed.downChange || downChange
-        )
-    )
-
 /**
  * A function used to react to and modify [PointerInputChange]s.
  */
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SimpleContainer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SimpleContainer.kt
deleted file mode 100644
index d294048c..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SimpleContainer.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.compose.ui.selection
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Layout
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.Placeable
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.enforce
-import androidx.compose.ui.unit.hasFixedHeight
-import androidx.compose.ui.unit.hasFixedWidth
-import kotlin.math.max
-
-/**
- * A Container Box implementation used for selection children and handle layout
- */
-@Composable
-internal fun SimpleContainer(
-    modifier: Modifier = Modifier,
-    width: Dp? = null,
-    height: Dp? = null,
-    children: @Composable () -> Unit
-) {
-    Layout(children, modifier) { measurables, incomingConstraints ->
-        val containerConstraints = Constraints()
-            .copy(
-                width?.toIntPx() ?: 0,
-                width?.toIntPx() ?: Constraints.Infinity,
-                height?.toIntPx() ?: 0,
-                height?.toIntPx() ?: Constraints.Infinity
-            )
-            .enforce(incomingConstraints)
-        val childConstraints = containerConstraints.copy(minWidth = 0, minHeight = 0)
-        var placeable: Placeable? = null
-        val containerWidth = if (
-            containerConstraints.hasFixedWidth
-        ) {
-            containerConstraints.maxWidth
-        } else {
-            placeable = measurables.firstOrNull()?.measure(childConstraints)
-            max((placeable?.width ?: 0), containerConstraints.minWidth)
-        }
-        val containerHeight = if (
-            containerConstraints.hasFixedHeight
-        ) {
-            containerConstraints.maxHeight
-        } else {
-            if (placeable == null) {
-                placeable = measurables.firstOrNull()?.measure(childConstraints)
-            }
-            max((placeable?.height ?: 0), containerConstraints.minHeight)
-        }
-        layout(containerWidth, containerHeight) {
-            val p = placeable ?: measurables.firstOrNull()?.measure(childConstraints)
-            p?.let {
-                val position = Alignment.Center.align(
-                    IntSize(
-                        containerWidth - it.width,
-                        containerHeight - it.height
-                    )
-                )
-                it.placeRelative(
-                    position.x,
-                    position.y
-                )
-            }
-        }
-    }
-}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
index c53b3c3..20eb925 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
@@ -17,24 +17,27 @@
 package androidx.compose.ui.platform
 
 import androidx.compose.ui.DrawLayerModifier
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.DesktopCanvas
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.asDesktopPath
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.graphics.toSkijaRect
 import androidx.compose.ui.node.OwnedLayer
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
-import org.jetbrains.skija.Matrix33
+import androidx.compose.ui.unit.toBounds
+import androidx.compose.ui.unit.toRect
 import org.jetbrains.skija.Picture
 import org.jetbrains.skija.PictureRecorder
 import org.jetbrains.skija.Point3
-import org.jetbrains.skija.Rect
 import org.jetbrains.skija.ShadowUtils
 
 class SkijaLayer(
@@ -46,6 +49,7 @@
     private var size = IntSize.Zero
     private var position = IntOffset.Zero
     private var outlineCache = OutlineCache(owner.density, size, modifier.shape)
+    private val matrix = Matrix()
     private val pictureRecorder = PictureRecorder()
     private var picture: Picture? = null
     private var isDestroyed = false
@@ -54,10 +58,11 @@
         set(value) {
             field = value
             outlineCache.shape = value.shape
+            updateMatrix()
             invalidate()
         }
 
-    override val layerId = 0L
+    override val layerId = lastId++
 
     override fun destroy() {
         picture?.close()
@@ -69,6 +74,7 @@
         if (size != this.size) {
             this.size = size
             outlineCache.size = size
+            updateMatrix()
             invalidate()
         }
     }
@@ -80,9 +86,29 @@
         }
     }
 
-    // TODO(demin): calculate matrix
     override fun getMatrix(matrix: Matrix) {
+        matrix.setFrom(this.matrix)
+    }
+
+    // TODO(demin): support perspective projection for rotationX/rotationY (as in Android)
+    private fun updateMatrix() {
+        val pivotX = modifier.transformOrigin.pivotFractionX * size.width
+        val pivotY = modifier.transformOrigin.pivotFractionY * size.height
+
         matrix.reset()
+        matrix *= Matrix().apply {
+            translate(x = -pivotX, y = -pivotY)
+        }
+        matrix *= Matrix().apply {
+            translate(modifier.translationX, modifier.translationY)
+            rotateX(modifier.rotationX)
+            rotateY(modifier.rotationY)
+            rotateZ(modifier.rotationZ)
+            scale(modifier.scaleX, modifier.scaleY)
+        }
+        matrix *= Matrix().apply {
+            translate(x = pivotX, y = pivotY)
+        }
     }
 
     override val isValid: Boolean
@@ -99,67 +125,52 @@
     override fun drawLayer(canvas: Canvas) {
         outlineCache.density = owner.density
         if (picture == null) {
-            val pictureCanvas = pictureRecorder.beginRecording(
-                Rect.makeWH(
-                    size.width.toFloat(),
-                    size.height.toFloat()
-                )
-            )
-            performDrawLayer(DesktopCanvas(pictureCanvas))
+            val bounds = size.toBounds().toRect()
+            val pictureCanvas = pictureRecorder.beginRecording(bounds.toSkijaRect())
+            performDrawLayer(DesktopCanvas(pictureCanvas), bounds)
             picture = pictureRecorder.finishRecordingAsPicture()
         }
-        canvas.nativeCanvas.drawPicture(
-            picture,
-            Matrix33.makeTranslate(position.x.toFloat(), position.y.toFloat()),
-            null
-        )
+
+        canvas.save()
+        canvas.concat(matrix)
+        canvas.translate(position.x.toFloat(), position.y.toFloat())
+        canvas.nativeCanvas.drawPicture(picture, null, null)
+        canvas.restore()
     }
 
-    // TODO(demin): implement alpha, rotationX, rotationY
-    @OptIn(ExperimentalUnsignedTypes::class)
-    private fun performDrawLayer(canvas: DesktopCanvas) {
-        canvas.save()
-
-        val pivotX = modifier.transformOrigin.pivotFractionX * size.width
-        val pivotY = modifier.transformOrigin.pivotFractionY * size.height
-
-        canvas.translate(modifier.translationX, modifier.translationY)
-        canvas.translate(pivotX, pivotY)
-
-        if (modifier.rotationZ != 0f) {
-            canvas.rotate(modifier.rotationZ)
-        }
-
-        if (modifier.scaleX != 1f || modifier.scaleY != 1f) {
-            canvas.scale(modifier.scaleX, modifier.scaleY)
-        }
-
-        canvas.translate(-pivotX, -pivotY)
-
-        if (modifier.shadowElevation > 0 && modifier.alpha != 0f) {
-            drawShadow(canvas)
-        }
-
-        if (modifier.clip && size != IntSize.Zero) {
-            when (val outline = outlineCache.outline) {
-                is Outline.Rectangle -> canvas.clipRect(outline.rect)
-                is Outline.Rounded -> canvas.clipRoundRect(outline.roundRect)
-                is Outline.Generic -> canvas.clipPath(outline.path)
+    private fun performDrawLayer(canvas: DesktopCanvas, bounds: Rect) {
+        if (modifier.alpha > 0) {
+            if (modifier.shadowElevation > 0) {
+                drawShadow(canvas)
             }
-        }
 
-        if (modifier.alpha != 0f) {
+            if (modifier.alpha < 1) {
+                canvas.saveLayer(
+                    bounds,
+                    Paint().apply { alpha = modifier.alpha }
+                )
+            } else {
+                canvas.save()
+            }
+
+            if (modifier.clip) {
+                when (val outline = outlineCache.outline) {
+                    is Outline.Rectangle -> canvas.clipRect(outline.rect)
+                    is Outline.Rounded -> canvas.clipRoundRect(outline.roundRect)
+                    is Outline.Generic -> canvas.clipPath(outline.path)
+                }
+            }
+
             drawBlock(canvas)
+            canvas.restore()
         }
-
-        canvas.restore()
     }
 
     override fun updateDisplayList() = Unit
 
     override fun updateLayerProperties() = Unit
 
-    @ExperimentalUnsignedTypes
+    @OptIn(ExperimentalUnsignedTypes::class)
     fun drawShadow(canvas: DesktopCanvas) = with (owner.density) {
         val path = when (val outline = outlineCache.outline) {
             is Outline.Rectangle -> Path().apply { addRect(outline.rect) }
@@ -175,8 +186,8 @@
         val lightPos = Point3(0f, 0f, 600.dp.toPx())
         val lightRad = 800.dp.toPx()
 
-        val ambientAlpha = 0.039f
-        val spotAlpha = 0.19f
+        val ambientAlpha = 0.039f * modifier.alpha
+        val spotAlpha = 0.19f * modifier.alpha
         val ambientColor = Color.Black.copy(alpha = ambientAlpha)
         val spotColor = Color.Black.copy(alpha = spotAlpha)
 
@@ -187,4 +198,8 @@
             spotColor.toArgb(), modifier.alpha < 1f, false
         )
     }
+
+    companion object {
+        private var lastId = 0L
+    }
 }
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt
index dc9f7a6..551156a 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt
@@ -16,8 +16,9 @@
 
 package androidx.compose.ui.platform
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.ui.Modifier
@@ -93,6 +94,70 @@
     }
 
     @Test
+    fun rotationX() {
+        val window = TestComposeWindow(width = 40, height = 40)
+
+        window.setContent {
+            Box(
+                Modifier
+                    .drawLayer(rotationX = 45f)
+                    .size(10f.dp, 10f.dp).background(Color.Blue)
+            )
+            Box(
+                Modifier
+                    .drawLayer(
+                        translationX = 20f,
+                        transformOrigin = TransformOrigin(0f, 0f),
+                        rotationX = 45f
+                    )
+                    .size(10f.dp, 10f.dp).background(Color.Blue)
+            )
+        }
+        screenshotRule.snap(window.surface)
+    }
+
+    @Test
+    fun rotationY() {
+        val window = TestComposeWindow(width = 40, height = 40)
+        window.setContent {
+            Box(
+                Modifier
+                    .drawLayer(rotationY = 45f)
+                    .size(10f.dp, 10f.dp).background(Color.Blue)
+            )
+            Box(
+                Modifier
+                    .drawLayer(
+                        translationX = 20f,
+                        transformOrigin = TransformOrigin(0f, 0f),
+                        rotationY = 45f
+                    )
+                    .size(10f.dp, 10f.dp).background(Color.Blue)
+            )
+        }
+        screenshotRule.snap(window.surface)
+    }
+
+    @Test
+    fun `nested layer transformations`() {
+        val window = TestComposeWindow(width = 40, height = 40)
+        window.setContent {
+            Box(
+                Modifier
+                    .drawLayer(rotationZ = 45f, translationX = 10f)
+                    .size(20f.dp, 20f.dp).background(Color.Green)
+            ) {
+                Box(
+                    Modifier
+                        .drawLayer(rotationZ = 45f)
+                        .size(20f.dp, 20f.dp).background(Color.Blue)
+                )
+            }
+        }
+        screenshotRule.snap(window.surface)
+    }
+
+    @Test
     fun clip() {
         val window = TestComposeWindow(width = 40, height = 40)
         window.setContent {
@@ -162,4 +227,101 @@
         }
         screenshotRule.snap(window.surface)
     }
+
+    @Test
+    fun alpha() {
+        val window = TestComposeWindow(width = 40, height = 40)
+        window.setContent {
+            Box(
+                Modifier
+                    .padding(start = 5.dp)
+                    .drawLayer(
+                        translationX = -5f,
+                        translationY = 5f,
+                        transformOrigin = TransformOrigin(0f, 0f),
+                        alpha = 0.5f
+                    )
+                    .size(10f.dp, 10f.dp)
+                    .background(Color.Green)
+            ) {
+                // This box will be clipped (because if we use alpha, we draw into
+                // intermediate buffer)
+                Box(
+                    Modifier
+                        .size(30f.dp, 30f.dp)
+                        .background(Color.Blue)
+                )
+            }
+
+            Box(
+                Modifier
+                    .padding(start = 15.dp)
+                    .drawLayer(alpha = 0.5f)
+                    .size(15f.dp, 15f.dp)
+                    .background(Color.Red)
+            ) {
+                Box(
+                    Modifier
+                        .drawLayer(alpha = 0.5f)
+                        .size(10f.dp, 10f.dp)
+                        .background(Color.Blue)
+                )
+            }
+
+            Box(
+                Modifier
+                    .drawLayer(
+                        alpha = 0f
+                    )
+                    .size(10f.dp, 10f.dp)
+                    .background(Color.Blue)
+            )
+        }
+        screenshotRule.snap(window.surface)
+    }
+
+    @Test
+    fun elevation() {
+        val window = TestComposeWindow(width = 40, height = 40)
+        window.setContent {
+            Box(
+                Modifier
+                    .drawLayer(shadowElevation = 5f)
+                    .size(20f.dp, 20f.dp)
+            )
+            Box(
+                Modifier
+                    .drawLayer(translationX = 20f, shadowElevation = 5f)
+                    .size(20f.dp, 20f.dp)
+            ) {
+                Box(
+                    Modifier
+                        .size(20f.dp, 20f.dp)
+                        .background(Color.Blue)
+                )
+            }
+            Box(
+                Modifier
+                    .drawLayer(translationY = 20f, alpha = 0.8f, shadowElevation = 5f)
+                    .size(20f.dp, 20f.dp)
+            ) {
+                Box(
+                    Modifier
+                        .size(20f.dp, 20f.dp)
+                        .background(Color.Red)
+                )
+            }
+            Box(
+                Modifier
+                    .drawLayer(
+                        translationX = 20f,
+                        translationY = 20f,
+                        shadowElevation = 5f,
+                        alpha = 0.8f
+                    )
+                    .size(20f.dp, 20f.dp)
+            )
+        }
+        screenshotRule.snap(window.surface)
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
new file mode 100644
index 0000000..3a2f260
--- /dev/null
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
@@ -0,0 +1,334 @@
+/*
+ * 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.compose.ui.platform
+
+import androidx.compose.ui.DrawLayerModifier
+import androidx.compose.ui.TransformOrigin
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import kotlin.math.PI
+import kotlin.math.cos
+import kotlin.math.roundToInt
+
+class SkijaLayerTest {
+    private val layer = TestSkijaLayer()
+    private val matrix = Matrix()
+    private val cos45 = cos(PI / 4)
+
+    @Test
+    fun initial() {
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun move() {
+        layer.move(IntOffset(10, 20))
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun resize() {
+        layer.resize(IntSize(100, 10))
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `resize and move`() {
+        layer.resize(IntSize(100, 10))
+        layer.move(IntOffset(10, 20))
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `translation, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            translationX = 10f,
+            translationY = 20f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(10, 20), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(110, 30), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `translation, bottom-right origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            translationX = 10f,
+            translationY = 20f,
+            transformOrigin = TransformOrigin(1f, 1f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(10, 20), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(110, 30), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `scale, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            scaleX = 2f,
+            scaleY = 4f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(200, 40), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `scale, bottom-right origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            scaleX = 2f,
+            scaleY = 4f,
+            transformOrigin = TransformOrigin(1f, 1f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(-100, -30), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `rotationX, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            rotationX = 45f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        val y = (10 * cos45).roundToInt()
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, y), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `rotationX, bottom-right origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            rotationX = 45f,
+            transformOrigin = TransformOrigin(1f, 1f)
+        )
+        layer.getMatrix(matrix)
+
+        val y = 10 * (1 - cos45.toFloat())
+        println(matrix.map(Offset(0f, 0f)))
+        println(matrix.map(Offset(100f, 10f)))
+        assertEquals(Offset(0f, y), matrix.map(Offset(0f, 0f)))
+        assertEquals(Offset(100f, 10f), matrix.map(Offset(100f, 10f)))
+    }
+
+    @Test
+    fun `rotationY, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            rotationY = 45f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        val x = (100 * cos45).roundToInt()
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(x, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `rotationY, bottom-right origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            rotationY = 45f,
+            transformOrigin = TransformOrigin(1f, 1f)
+        )
+        layer.getMatrix(matrix)
+
+        val x = (100 * (1 - cos45)).roundToInt()
+        assertEquals(IntOffset(x, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `rotationZ, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            rotationZ = 90f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(-10, 100), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `rotationZ, bottom-right origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            rotationZ = 90f,
+            transformOrigin = TransformOrigin(1f, 1f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(110, -90), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100, 10), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `translation, scale, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            translationX = 60f,
+            translationY = 7f,
+            scaleX = 2f,
+            scaleY = 4f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0 + 60, 0 + 7), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100 * 2 + 60, 10 * 4 + 7), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `translation, rotationZ, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            translationX = 60f,
+            translationY = 7f,
+            rotationZ = 90f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0 + 60, 0 + 7), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(-10 + 60, 100 + 7), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `translation, rotationX, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            translationX = 60f,
+            translationY = 7f,
+            rotationX = 45f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        val y = (10 * cos45).roundToInt()
+        val translationY = (7 * cos45).roundToInt()
+        assertEquals(IntOffset(0 + 60, 0 + translationY), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(100 + 60, y + translationY), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `translation, rotationY, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            translationX = 60f,
+            translationY = 7f,
+            rotationY = 45f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        val x = (100 * cos45).roundToInt()
+        val translationX = (60 * cos45).roundToInt()
+        assertEquals(IntOffset(0 + translationX, 0 + 7), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(x + translationX, 10 + 7), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `scale, rotationZ, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            scaleX = 2f,
+            scaleY = 4f,
+            rotationZ = 90f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0, 0), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(-10 * 4, 100 * 2), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    @Test
+    fun `translation, scale, rotationZ, left-top origin`() {
+        layer.resize(IntSize(100, 10))
+        layer.modifier = SimpleDrawLayerModifier(
+            translationX = 60f,
+            translationY = 7f,
+            scaleX = 2f,
+            scaleY = 4f,
+            rotationZ = 90f,
+            transformOrigin = TransformOrigin(0f, 0f)
+        )
+        layer.getMatrix(matrix)
+
+        assertEquals(IntOffset(0 + 60, 0 + 7), matrix.map(Offset(0f, 0f)).round())
+        assertEquals(IntOffset(-10 * 4 + 60, 100 * 2 + 7), matrix.map(Offset(100f, 10f)).round())
+    }
+
+    private fun TestSkijaLayer() = SkijaLayer(
+        density = Density(1f, 1f),
+        modifier = SimpleDrawLayerModifier(),
+        invalidateParentLayer = {},
+        drawBlock = {}
+    )
+
+    private data class SimpleDrawLayerModifier(
+        override val scaleX: Float = 1f,
+        override val scaleY: Float = 1f,
+        override val alpha: Float = 1f,
+        override val translationX: Float = 0f,
+        override val translationY: Float = 0f,
+        override val shadowElevation: Float = 0f,
+        override val rotationX: Float = 0f,
+        override val rotationY: Float = 0f,
+        override val rotationZ: Float = 0f,
+        override val transformOrigin: TransformOrigin = TransformOrigin.Center,
+        override val shape: Shape = RectangleShape,
+        override val clip: Boolean = false
+    ) : DrawLayerModifier
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt
index 126c0a1..86d9555 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt
@@ -23,8 +23,8 @@
 import androidx.compose.ui.gesture.customevents.DelayUpMessage
 import androidx.compose.ui.input.pointer.CustomEventDispatcher
 import androidx.compose.ui.input.pointer.PointerId
-import androidx.compose.ui.input.pointer.consume
 import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.down
 import androidx.compose.ui.input.pointer.invokeOverAllPasses
 import androidx.compose.ui.input.pointer.moveTo
@@ -136,7 +136,8 @@
     @Test
     fun onPointerEvent_downMoveConsumedUpDownInsideTimeoutUp_onDoubleTapNotCalled() {
         val down1 = down(1, 0.milliseconds)
-        val moveConsumed = down1.moveTo(1.milliseconds, x = 1f).consume(dx = 1f)
+        val moveConsumed =
+            down1.moveTo(1.milliseconds, x = 1f).apply { consumePositionChange(1f, 0f) }
         val up1 = moveConsumed.up(duration = 2.milliseconds)
         val down2 = down(2, 101.milliseconds)
         val up2 = down2.up(duration = 102.milliseconds)
@@ -156,7 +157,8 @@
         val down1 = down(1, 0.milliseconds)
         val up1 = down1.up(duration = 1.milliseconds)
         val down2 = down(2, 100.milliseconds)
-        val moveConsumed = down2.moveTo(101.milliseconds, x = 1f).consume(dx = 1f)
+        val moveConsumed =
+            down2.moveTo(101.milliseconds, x = 1f).apply { consumePositionChange(1f, 0f) }
         val up2 = moveConsumed.up(duration = 102.milliseconds)
 
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down1))
@@ -173,7 +175,8 @@
     fun onPointerEvent_2Down1MoveConsumedUpDownInsideTimeoutUp_onDoubleTapNotCalled() {
         val down1A = down(0, 0.milliseconds)
         val down1B = down(1, 0.milliseconds)
-        val moveConsumed1A = down1A.moveTo(1.milliseconds, x = 1f).consume(dx = 1f)
+        val moveConsumed1A =
+            down1A.moveTo(1.milliseconds, x = 1f).apply { consumePositionChange(1f, 0f) }
         val move1B = down1B.moveTo(1.milliseconds)
         val up1A = moveConsumed1A.up(duration = 2.milliseconds)
         val up1B = move1B.up(duration = 2.milliseconds)
@@ -196,7 +199,8 @@
         val up2 = down1.up(duration = 1.milliseconds)
         val down2A = down(0, 100.milliseconds)
         val down2B = down(1, 100.milliseconds)
-        val moveConsumed2A = down2A.moveTo(101.milliseconds, x = 1f).consume(dx = 1f)
+        val moveConsumed2A =
+            down2A.moveTo(101.milliseconds, x = 1f).apply { consumePositionChange(1f, 0f) }
         val move2B = down2B.moveTo(101.milliseconds)
         val up2A = moveConsumed2A.up(duration = 102.milliseconds)
         val up2B = move2B.up(duration = 102.milliseconds)
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt
index cce4b9f..778a789 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt
@@ -24,8 +24,8 @@
 import androidx.compose.ui.gesture.scrollorientationlocking.ScrollOrientationLocker
 import androidx.compose.ui.gesture.scrollorientationlocking.ShareScrollOrientationLockerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.consume
 import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.down
 import androidx.compose.ui.input.pointer.invokeOverAllPasses
 import androidx.compose.ui.input.pointer.invokeOverPass
@@ -101,7 +101,9 @@
     fun onPointerEvent_downMoveFullyConsumed_canDragNotCalled() {
         val down = down(0)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down))
-        val move = down.moveBy(Duration(milliseconds = 10), 3f, 5f).consume(3f, 5f)
+        val move =
+            down.moveBy(Duration(milliseconds = 10), 3f, 5f)
+                .apply { consumePositionChange(3f, 5f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(move))
 
         assertThat(canDragDirections).isEmpty()
@@ -136,7 +138,9 @@
     fun onPointerEvent_downMoveOneDimensionPartiallyConsumed_canDragCalledOnce() {
         val down = down(0)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down))
-        val move = down.moveBy(Duration(milliseconds = 10), 0f, 5f).consume(0f, 4f)
+        val move =
+            down.moveBy(Duration(milliseconds = 10), 0f, 5f)
+                .apply { consumePositionChange(0f, 4f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(move))
 
         // Twice because while under touch slop, DragGestureDetector checks during Main and
@@ -148,7 +152,9 @@
     fun onPointerEvent_downMoveTwoDimensionPartiallyConsumed_canDragCalledTwice() {
         val down = down(0)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down))
-        val move = down.moveBy(Duration(milliseconds = 10), 3f, 5f).consume(2f, 4f)
+        val move =
+            down.moveBy(Duration(milliseconds = 10), 3f, 5f)
+                .apply { consumePositionChange(2f, 4f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(move))
 
         // 4 times because while under touch slop, DragGestureDetector checks during Main and
@@ -303,7 +309,9 @@
         val down = down(0)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down))
 
-        val move = down.moveBy(10.milliseconds, TestTouchSlop + TinyNum, 0f).consume(dx = 1f)
+        val move =
+            down.moveBy(10.milliseconds, TestTouchSlop + TinyNum, 0f)
+                .apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(move))
 
         // Assert
@@ -325,7 +333,8 @@
                 PointerEventPass.Main
             )
         )
-        val move2 = move.consume(dx = (TestTouchSlop * 2f + TinyNum))
+        val move2 =
+            move.apply { consumePositionChange(TestTouchSlop * 2f + TinyNum, 0f) }
         filter::onPointerEvent.invokeOverPass(
             pointerEventOf(move2),
             PointerEventPass.Final
@@ -684,7 +693,8 @@
         val down = down(0)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down))
 
-        val move = down.moveBy(10.milliseconds, 0f, 0f).consume(dx = beyondSlop)
+        val move =
+            down.moveBy(10.milliseconds, 0f, 0f).apply { consumePositionChange(beyondSlop, 0f) }
         filter::onPointerEvent.invokeOverPasses(
             pointerEventOf(move),
             listOf(
@@ -714,7 +724,7 @@
             )
         )
 
-        val moveConsumed = move.consume(dx = beyondSlop)
+        val moveConsumed = move.apply { consumePositionChange(beyondSlop, 0f) }
         filter::onPointerEvent.invokeOverPasses(
             pointerEventOf(moveConsumed),
             PointerEventPass.Final
@@ -742,7 +752,7 @@
             )
         )
 
-        val moveConsumed = move.consume(dx = -restOfSlopAndBeyond)
+        val moveConsumed = move.apply { consumePositionChange(-restOfSlopAndBeyond, 0f) }
         filter::onPointerEvent.invokeOverPasses(
             pointerEventOf(moveConsumed),
             PointerEventPass.Final
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/LongPressGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/LongPressGestureFilterTest.kt
index 37b517e..2ddd50a 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/LongPressGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/LongPressGestureFilterTest.kt
@@ -20,8 +20,8 @@
 import androidx.compose.ui.gesture.customevents.LongPressFiredEvent
 import androidx.compose.ui.input.pointer.CustomEventDispatcher
 import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.consume
 import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.down
 import androidx.compose.ui.input.pointer.invokeOverAllPasses
 import androidx.compose.ui.input.pointer.moveBy
@@ -82,7 +82,8 @@
     @Test
     fun onPointerEvent_DownMoveConsumed_eventNotFired() {
         val down = down(0)
-        val move = down.moveBy(50.milliseconds, 1f, 1f).consume(1f, 0f)
+        val move =
+            down.moveBy(50.milliseconds, 1f, 1f).apply { consumePositionChange(1f, 0f) }
 
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down))
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
@@ -97,7 +98,8 @@
     fun onPointerEvent_2Down1MoveConsumed_eventNotFired() {
         val down0 = down(0)
         val down1 = down(1)
-        val move0 = down0.moveBy(50.milliseconds, 1f, 1f).consume(1f, 0f)
+        val move0 =
+            down0.moveBy(50.milliseconds, 1f, 1f).apply { consumePositionChange(1f, 0f) }
         val move1 = down0.moveBy(50.milliseconds, 0f, 0f)
 
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down0, down1))
@@ -450,7 +452,7 @@
         var pointer = down(0, 0.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        pointer = pointer.moveTo(50.milliseconds, 5f).consume(1f)
+        pointer = pointer.moveTo(50.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
 
         // Act
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilterTest.kt
index 24b2c0e..f9ebc0b 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilterTest.kt
@@ -18,8 +18,8 @@
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.consume
 import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.down
 import androidx.compose.ui.input.pointer.invokeOverAllPasses
 import androidx.compose.ui.input.pointer.invokeOverPasses
@@ -128,7 +128,7 @@
     fun onPointerInput_downMoveConsumedUp_onStopNotCalled() {
         var pointer = down(0, 0.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
-        pointer = pointer.moveTo(100.milliseconds, 5f).consume(1f)
+        pointer = pointer.moveTo(100.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
         pointer = pointer.up(200.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
@@ -233,7 +233,7 @@
     fun onPointerInput_downConsumedMoveConsumed_onCancelNotCalled() {
         var pointer = down(0, 0.milliseconds).apply { consumeDownChange() }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
-        pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
+        pointer = pointer.moveBy(100.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
 
         verify(filter.onCancel!!, never()).invoke()
@@ -290,7 +290,7 @@
     fun onPointerInput_downMoveConsumed_onCancelCalledOnce() {
         var pointer = down(0, 0.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
-        pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
+        pointer = pointer.moveBy(100.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
 
         verify(filter.onCancel!!).invoke()
@@ -300,9 +300,9 @@
     fun onPointerInput_downMoveConsumedMoveConsumed_onCancelCalledOnce() {
         var pointer = down(0, 0.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
-        pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
+        pointer = pointer.moveBy(100.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
-        pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
+        pointer = pointer.moveBy(100.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
 
         verify(filter.onCancel!!).invoke()
@@ -313,8 +313,8 @@
         var pointer0 = down(0)
         var pointer1 = down(1)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer0, pointer1))
-        pointer0 = pointer0.moveBy(100.milliseconds, 5f).consume(1f)
-        pointer1 = pointer1.moveBy(100.milliseconds, 5f).consume(1f)
+        pointer0 = pointer0.moveBy(100.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
+        pointer1 = pointer1.moveBy(100.milliseconds, 5f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer0, pointer1))
 
         verify(filter.onCancel!!).invoke()
@@ -327,11 +327,11 @@
         pointer0 = pointer0.moveTo(100.milliseconds)
         var pointer1 = down(1, duration = 100.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer0, pointer1))
-        pointer0 = pointer0.moveBy(100L.milliseconds, 5f).consume(5f)
+        pointer0 = pointer0.moveBy(100L.milliseconds, 5f).apply { consumePositionChange(5f, 0f) }
         pointer1 = pointer1.moveBy(100L.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer0, pointer1))
         pointer0 = pointer0.moveBy(100L.milliseconds)
-        pointer1 = pointer1.moveBy(100L.milliseconds, 5f).consume(5f)
+        pointer1 = pointer1.moveBy(100L.milliseconds, 5f).apply { consumePositionChange(5f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer0, pointer1))
 
         verify(filter.onCancel!!).invoke()
@@ -544,7 +544,7 @@
     fun onCancel_downMoveConsumedCancel_justStartAndCancelCalledInOrderOnce() {
         var pointer = down(0, 0.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
-        pointer = pointer.moveTo(50.milliseconds, 1f).consume(1f)
+        pointer = pointer.moveTo(50.milliseconds, 1f).apply { consumePositionChange(1f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
         filter.onCancel()
 
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawDragGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawDragGestureFilterTest.kt
index 8fa8ae4..f75ba75 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawDragGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawDragGestureFilterTest.kt
@@ -23,7 +23,6 @@
 import androidx.compose.ui.input.pointer.CustomEventDispatcher
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.anyPositionChangeConsumed
-import androidx.compose.ui.input.pointer.consume
 import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.down
 import androidx.compose.ui.input.pointer.invokeOverAllPasses
@@ -105,7 +104,8 @@
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down1))
         dragStartBlocked = false
 
-        val move1 = down1.moveBy(10.milliseconds, 1f, 1f).consume(dx = 1f, dy = 1f)
+        val move1 =
+            down1.moveBy(10.milliseconds, 1f, 1f).apply { consumePositionChange(1f, 1f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(move1))
 
         assertThat(log.filter { it.methodName == "onStart" }).isEmpty()
@@ -202,7 +202,7 @@
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(down))
         dragStartBlocked = false
 
-        val move = down.moveBy(10.milliseconds, 1f, 0f).consume(dx = 2f)
+        val move = down.moveBy(10.milliseconds, 1f, 0f).apply { consumePositionChange(2f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(move))
 
         assertThat(log.filter { it.methodName == "onStart" }).hasSize(1)
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilterTest.kt
index 8f71ec8..1b8ec4d 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilterTest.kt
@@ -19,7 +19,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.anyPositionChangeConsumed
-import androidx.compose.ui.input.pointer.consume
+import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.down
 import androidx.compose.ui.input.pointer.invokeOverAllPasses
 import androidx.compose.ui.input.pointer.invokeOverPasses
@@ -276,9 +276,9 @@
         scaleStartBlocked = false
 
         pointer1 =
-            pointer1.moveTo(10.milliseconds, 2f, 2f).consume(-1f, -2f)
+            pointer1.moveTo(10.milliseconds, 2f, 2f).apply { consumePositionChange(-1f, -2f) }
         pointer2 =
-            pointer2.moveTo(10.milliseconds, 5f, 1f).consume(1f, -2f)
+            pointer2.moveTo(10.milliseconds, 5f, 1f).apply { consumePositionChange(1f, -2f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer1, pointer2))
 
         assertThat(log.filter { it.methodName == "onStart" }).isEmpty()
@@ -327,7 +327,10 @@
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer1, pointer2))
         scaleStartBlocked = false
 
-        pointer1 = pointer1.moveBy(10.milliseconds, dx = 0f, dy = 0f).consume(dx = -1f)
+        pointer1 =
+            pointer1.moveBy(10.milliseconds, dx = 0f, dy = 0f).apply {
+                consumePositionChange(-1f, 0f)
+            }
         pointer2 = pointer2.moveBy(10.milliseconds, dx = 0f, dy = 0f)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer1, pointer2))
 
@@ -343,7 +346,10 @@
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer1, pointer2))
         scaleStartBlocked = false
 
-        pointer1 = pointer1.moveBy(10.milliseconds, dx = 0f, dy = 0f).consume(dy = -1f)
+        pointer1 =
+            pointer1
+                .moveBy(10.milliseconds, dx = 0f, dy = 0f)
+                .apply { consumePositionChange(0f, -1f) }
         pointer2 = pointer2.moveBy(10.milliseconds, dx = 0f, dy = 0f)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer1, pointer2))
 
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt
index f35d3c3..04e2806 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt
@@ -23,8 +23,8 @@
 import androidx.compose.ui.gesture.customevents.DelayUpMessage
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerId
-import androidx.compose.ui.input.pointer.consume
 import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.down
 import androidx.compose.ui.input.pointer.invokeOverAllPasses
 import androidx.compose.ui.input.pointer.invokeOverPass
@@ -79,7 +79,7 @@
     fun onPointerEvent_downMoveConsumedUp_onReleaseNotCalled() {
         var pointer = down(0, 0.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
-        pointer = pointer.moveTo(100.milliseconds, 5f).consume(5f)
+        pointer = pointer.moveTo(100.milliseconds, 5f).apply { consumePositionChange(5f, 0f) }
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
         pointer = pointer.up(200.milliseconds)
         filter::onPointerEvent.invokeOverAllPasses(pointerEventOf(pointer))
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt
index 2a193ce..14310eb 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt
@@ -20,6 +20,9 @@
 import androidx.compose.ui.unit.Uptime
 import androidx.compose.ui.unit.milliseconds
 import androidx.test.filters.SmallTest
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.core.IsEqual.equalTo
@@ -435,18 +438,12 @@
         val pointerInputChangeResult3 =
             pointerInputChange1.deepCopy().apply { consumePositionChange(5f, 3f) }
 
-        assertThat(
-            pointerInputChangeResult1,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 5f, 0f, false)))
-        )
-        assertThat(
-            pointerInputChangeResult2,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 0f, 3f, false)))
-        )
-        assertThat(
-            pointerInputChangeResult3,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 5f, 3f, false)))
-        )
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult1).positionChangeConsumed(Offset(5f, 0f))
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult2).positionChangeConsumed(Offset(0f, 3f))
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult3).positionChangeConsumed(Offset(5f, 3f))
     }
 
     @Test
@@ -461,18 +458,12 @@
         val pointerInputChangeResult3 =
             pointerInputChange1.deepCopy().apply { consumePositionChange(6f, 12f) }
 
-        assertThat(
-            pointerInputChangeResult1,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 6f, 0f, false)))
-        )
-        assertThat(
-            pointerInputChangeResult2,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 0f, 12f, false)))
-        )
-        assertThat(
-            pointerInputChangeResult3,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 6f, 12f, false)))
-        )
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult1).positionChangeConsumed(Offset(6f, 0f))
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult2).positionChangeConsumed(Offset(0f, 12f))
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult3).positionChangeConsumed(Offset(6f, 12f))
     }
 
     @Test
@@ -487,18 +478,12 @@
         val pointerInputChangeResult3 =
             pointerInputChange1.deepCopy().apply { consumePositionChange(2f, 3f) }
 
-        assertThat(
-            pointerInputChangeResult1,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 3f, 5f, false)))
-        )
-        assertThat(
-            pointerInputChangeResult2,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 1f, 8f, false)))
-        )
-        assertThat(
-            pointerInputChangeResult3,
-            `is`(equalTo(createPointerInputChange(8f, 16f, true, 2f, 4f, true, 3f, 8f, false)))
-        )
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult1).positionChangeConsumed(Offset(3f, 5f))
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult2).positionChangeConsumed(Offset(1f, 8f))
+        PointerInputChangeSubject
+            .assertThat(pointerInputChangeResult3).positionChangeConsumed(Offset(3f, 8f))
     }
 
     @Test
@@ -511,8 +496,10 @@
         val actual1 = pointerInputChange1.apply { consumeAllChanges() }
         val actual2 = pointerInputChange2.apply { consumeAllChanges() }
 
-        assertThat(actual1).isEqualTo(pointerInputChange1)
-        assertThat(actual2).isEqualTo(pointerInputChange2)
+        PointerInputChangeSubject
+            .assertThat(actual1).nothingConsumed()
+        PointerInputChangeSubject
+            .assertThat(actual2).nothingConsumed()
     }
 
     @Test
@@ -525,12 +512,8 @@
         val actual1 = pointerInputChange1.apply { consumeAllChanges() }
         val actual2 = pointerInputChange2.apply { consumeAllChanges() }
 
-        assertThat(actual1).isEqualTo(
-            createPointerInputChange(1f, 2f, true, 1f, 2f, false, 0f, 0f, true)
-        )
-        assertThat(actual2).isEqualTo(
-            createPointerInputChange(2f, 1f, false, 2f, 1f, true, 0f, 0f, true)
-        )
+        PointerInputChangeSubject.assertThat(actual1).downConsumed()
+        PointerInputChangeSubject.assertThat(actual2).downConsumed()
     }
 
     @Test
@@ -540,9 +523,7 @@
 
         val actual = pointerInputChange.apply { consumeAllChanges() }
 
-        assertThat(actual).isEqualTo(
-            createPointerInputChange(1f, 2f, true, 11f, 21f, true, -10f, -19f, false)
-        )
+        PointerInputChangeSubject.assertThat(actual).positionChangeConsumed(Offset(-10f, -19f))
     }
 
     @Test
@@ -552,13 +533,11 @@
 
         val actual = pointerInputChange.apply { consumeAllChanges() }
 
-        assertThat(actual).isEqualTo(
-            createPointerInputChange(1f, 2f, true, 11f, 21f, true, -10f, -19f, false)
-        )
+        PointerInputChangeSubject.assertThat(actual).positionChangeConsumed(Offset(-10f, -19f))
     }
 
     @Test
-    fun consumeAllChanges_allChanged_movementFullyConsumed() {
+    fun consumeAllChanges_allChanged_allConsumed() {
         val pointerInputChange1 =
             createPointerInputChange(1f, 2f, true, 11f, 21f, false, -3f, -5f, false)
         val pointerInputChange2 =
@@ -567,12 +546,10 @@
         val actual1 = pointerInputChange1.apply { consumeAllChanges() }
         val actual2 = pointerInputChange2.apply { consumeAllChanges() }
 
-        assertThat(actual1).isEqualTo(
-            createPointerInputChange(1f, 2f, true, 11f, 21f, false, -10f, -19f, true)
-        )
-        assertThat(actual2).isEqualTo(
-            createPointerInputChange(1f, 2f, false, 11f, 21f, true, -10f, -19f, true)
-        )
+        PointerInputChangeSubject.assertThat(actual1).downConsumed()
+        PointerInputChangeSubject.assertThat(actual1).positionChangeConsumed(Offset(-10f, -19f))
+        PointerInputChangeSubject.assertThat(actual2).downConsumed()
+        PointerInputChangeSubject.assertThat(actual2).positionChangeConsumed(Offset(-10f, -19f))
     }
 
     // Private Helper
@@ -611,10 +588,51 @@
     }
 }
 
+private class PointerInputChangeSubject(
+    metaData: FailureMetadata,
+    val actual: PointerInputChange
+) : Subject(metaData, actual) {
+
+    companion object {
+
+        private val Factory =
+            Factory<PointerInputChangeSubject, PointerInputChange> { metadata, actual ->
+                PointerInputChangeSubject(metadata, actual)
+            }
+
+        fun assertThat(actual: PointerInputChange): PointerInputChangeSubject {
+            return Truth.assertAbout(Factory).that(actual)
+        }
+    }
+
+    fun nothingConsumed() {
+        check("consumed.downChange").that(actual.consumed.downChange).isEqualTo(false)
+        check("consumed.positionChange").that(actual.consumed.positionChange).isEqualTo(Offset.Zero)
+    }
+
+    fun downConsumed() {
+        check("consumed.downChange").that(actual.consumed.downChange).isEqualTo(true)
+    }
+
+    fun downNotConsumed() {
+        check("consumed.downChange").that(actual.consumed.downChange).isEqualTo(false)
+    }
+
+    fun positionChangeConsumed(expected: Offset) {
+        check("consumed.positionChangeConsumed")
+            .that(actual.consumed.positionChange)
+            .isEqualTo(expected)
+    }
+
+    fun positionChangeNotConsumed() {
+        positionChangeConsumed(Offset.Zero)
+    }
+}
+
 private fun PointerInputChange.deepCopy() =
     PointerInputChange(
         id,
         current.copy(),
         previous.copy(),
-        consumed.copy()
+        ConsumedData(consumed.positionChange, consumed.downChange)
     )
\ No newline at end of file
diff --git a/development/build_log_processor.sh b/development/build_log_processor.sh
index fa46d1c..3f757b3 100755
--- a/development/build_log_processor.sh
+++ b/development/build_log_processor.sh
@@ -68,7 +68,7 @@
 if "$programName" "$@" > >(tee -a "$logFile") 2>&1; then
   if [ "$validateNoUnrecognizedMessagesOnSuccess" == "true" ]; then
     if $SCRIPT_PATH/build_log_simplifier.py --validate $logFile >&2; then
-      echo No unrecognized messages found in build log >&2
+      echo No unrecognized messages found in build log
     else
       echo >&2
       echo "Build log validation, enabled by the argument $validateArgument, failed" >&2
diff --git a/navigation/settings.gradle b/navigation/settings.gradle
index fdeeda2..6b6da84 100644
--- a/navigation/settings.gradle
+++ b/navigation/settings.gradle
@@ -19,6 +19,8 @@
 apply from: "../playground-common/playground-include-settings.gradle"
 setupPlayground(this, "..")
 selectProjectsFromAndroidX({ name ->
+    // Compose projects are not supported in playground yet
+    if (name.startsWith(":navigation:navigation-compose")) return false
     if (name.startsWith(":navigation")) return true
     if (name.startsWith(":internal-testutils-navigation")) return true
     if (name.startsWith(":internal-testutils-runtime")) return true
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index 0788904..41d2747 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -70,7 +70,11 @@
     }
 
     override fun findGeneratedAnnotation(): XTypeElement? {
-        TODO("Not yet implemented")
+        // this almost replicates what GeneratedAnnotations does except it doesn't check source
+        // version because we don't have that property here yet. Instead, it tries the new one
+        // first and falls back to the old one.
+        return findTypeElement("javax.annotation.processing.Generated")
+            ?: findTypeElement("javax.annotation.Generated")
     }
 
     override fun getDeclaredType(type: XTypeElement, vararg types: XType): XDeclaredType {
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
index 0187061..2ea0df4 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
@@ -173,6 +173,14 @@
     }
 
     @Test
+    fun findGeneratedAnnotation() {
+        runProcessorTestIncludingKsp { invocation ->
+            val generatedAnnotation = invocation.processingEnv.findGeneratedAnnotation()
+            assertThat(generatedAnnotation?.name).isEqualTo("Generated")
+        }
+    }
+
+    @Test
     fun generateCode() {
         val javaSrc = Source.java(
             "foo.bar.AccessGenerated",
diff --git a/wear/wear-watchface-data/api/restricted_current.txt b/wear/wear-watchface-data/api/restricted_current.txt
index 8ef12ce..7ae8729 100644
--- a/wear/wear-watchface-data/api/restricted_current.txt
+++ b/wear/wear-watchface-data/api/restricted_current.txt
@@ -225,19 +225,6 @@
     field public static final android.os.Parcelable.Creator<androidx.wear.watchface.data.IdAndComplicationDetails!>! CREATOR;
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public final class IndicatorState implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
-    ctor public IndicatorState(boolean, boolean, boolean, boolean, boolean, boolean);
-    method public int describeContents();
-    method public boolean getInAirplaneMode();
-    method public boolean getInTheaterMode();
-    method public boolean getIsCharging();
-    method public boolean getIsConnectedToCompanion();
-    method public boolean getIsGpsActive();
-    method public boolean getIsKeyguardLocked();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.wear.watchface.data.IndicatorState!>! CREATOR;
-  }
-
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public class RenderParametersWireFormat implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
     ctor public RenderParametersWireFormat(int, java.util.List<androidx.wear.watchface.data.RenderParametersWireFormat.LayerParameterWireFormat!>);
     method public int describeContents();
@@ -257,12 +244,10 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public final class SystemState implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
-    ctor public SystemState(boolean, int, int, int);
+    ctor public SystemState(boolean, int);
     method public int describeContents();
     method public boolean getInAmbientMode();
     method public int getInterruptionFilter();
-    method public int getNotificationCount();
-    method public int getUnreadCount();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<androidx.wear.watchface.data.SystemState!>! CREATOR;
   }
diff --git a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/IndicatorState.java b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/IndicatorState.java
deleted file mode 100644
index b7ad20b..0000000
--- a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/IndicatorState.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.wear.watchface.data;
-
-import android.annotation.SuppressLint;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.ParcelUtils;
-import androidx.versionedparcelable.VersionedParcelable;
-import androidx.versionedparcelable.VersionedParcelize;
-
-/**
- * Data sent over AIDL for {@link IWatchFaceCommand#setIndicatorState}.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-@VersionedParcelize
-@SuppressLint("BanParcelableUsage") // TODO(b/169214666): Remove Parcelable
-public final class IndicatorState implements VersionedParcelable, Parcelable {
-    @ParcelField(1)
-    boolean mIsCharging;
-
-    @ParcelField(2)
-    boolean mInAirplaneMode;
-
-    @ParcelField(3)
-    boolean mIsConnectedToCompanion;
-
-    @ParcelField(4)
-    boolean mInTheaterMode;
-
-    @ParcelField(5)
-    boolean mIsGpsActive;
-
-    @ParcelField(6)
-    boolean mIsKeyguardLocked;
-
-    /** Used by VersionedParcelable. */
-    IndicatorState() {}
-
-    public IndicatorState(
-            boolean isCharging,
-            boolean inAirplaneMode,
-            boolean isConnectedToCompanion,
-            boolean inTheaterMode,
-            boolean isGpsActive,
-            boolean isKeyguardLocked) {
-        mIsCharging = isCharging;
-        mInAirplaneMode = inAirplaneMode;
-        mIsConnectedToCompanion = isConnectedToCompanion;
-        mInTheaterMode = inTheaterMode;
-        mIsGpsActive = isGpsActive;
-        mIsKeyguardLocked = isKeyguardLocked;
-    }
-
-    public boolean getIsCharging() {
-        return mIsCharging;
-    }
-
-    public boolean getInAirplaneMode() {
-        return mInAirplaneMode;
-    }
-
-    public boolean getIsConnectedToCompanion() {
-        return mIsConnectedToCompanion;
-    }
-
-    public boolean getInTheaterMode() {
-        return mInTheaterMode;
-    }
-
-    public boolean getIsGpsActive() {
-        return mIsGpsActive;
-    }
-
-    public boolean getIsKeyguardLocked() {
-        return mIsKeyguardLocked;
-    }
-
-    /** Serializes this IndicatorState to the specified {@link Parcel}. */
-    @Override
-    public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        parcel.writeParcelable(ParcelUtils.toParcelable(this), flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final Parcelable.Creator<IndicatorState> CREATOR =
-            new Parcelable.Creator<IndicatorState>() {
-                @Override
-                public IndicatorState createFromParcel(Parcel source) {
-                    return IndicatorStateParcelizer.read(
-                            ParcelUtils.fromParcelable(source.readParcelable(
-                                    getClass().getClassLoader())));
-                }
-
-                @Override
-                public IndicatorState[] newArray(int size) {
-                    return new IndicatorState[size];
-                }
-            };
-}
diff --git a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/SystemState.java b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/SystemState.java
index d71cf20..09ae0a2 100644
--- a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/SystemState.java
+++ b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/SystemState.java
@@ -42,24 +42,14 @@
     @ParcelField(2)
     int mInterruptionFilter;
 
-    @ParcelField(3)
-    int mUnreadCount;
-
-    @ParcelField(4)
-    int mNotificationCount;
-
     /** Used by VersionedParcelable. */
     SystemState() {}
 
     public SystemState(
             boolean inAmbientMode,
-            int interruptionFilter,
-            int unreadCount,
-            int notificationCount) {
+            int interruptionFilter) {
         mInAmbientMode = inAmbientMode;
         mInterruptionFilter = interruptionFilter;
-        mUnreadCount = unreadCount;
-        mNotificationCount = notificationCount;
     }
 
     public boolean getInAmbientMode() {
@@ -70,14 +60,6 @@
         return mInterruptionFilter;
     }
 
-    public int getUnreadCount() {
-        return mUnreadCount;
-    }
-
-    public int getNotificationCount() {
-        return mNotificationCount;
-    }
-
     /** Serializes this SystemState to the specified {@link Parcel}. */
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index 6898c51..69cfde5 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -196,7 +196,6 @@
     method public androidx.wear.watchface.WatchFace.Builder setPreviewReferenceTimeMillis(long previewReferenceTimeMillis);
     method public androidx.wear.watchface.WatchFace.Builder setWear2AccentColor(@ColorInt int accentColor);
     method public androidx.wear.watchface.WatchFace.Builder setWear2AcceptsTapEvents(boolean acceptsTapEvents);
-    method public androidx.wear.watchface.WatchFace.Builder setWear2ShowUnreadCountIndicator(boolean showUnreadCountIndicator);
     method public androidx.wear.watchface.WatchFace.Builder setWear2StatusBarGravity(int statusBarGravity);
     method public androidx.wear.watchface.WatchFace.Builder setWear2ViewProtectionMode(int viewProtectionMode);
   }
@@ -218,13 +217,11 @@
   }
 
   public final class WatchState {
-    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> inAirplaneMode, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isConnectedToCompanion, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isGpsActive, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isKeyguardLocked, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isInTheaterMode, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, androidx.wear.watchface.ObservableWatchData<java.lang.Integer> notificationCount, androidx.wear.watchface.ObservableWatchData<java.lang.Integer> unreadNotificationCount, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape);
+    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape);
     method public boolean getHasBurnInProtection();
     method public boolean getHasLowBitAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getNotificationCount();
     method public int getScreenShape();
-    method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getUnreadNotificationCount();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
     property public final boolean hasBurnInProtection;
@@ -232,9 +229,7 @@
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible;
-    property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> notificationCount;
     property public final int screenShape;
-    property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> unreadNotificationCount;
     field public static final androidx.wear.watchface.WatchState.Companion Companion;
     field public static final int SCREEN_SHAPE_RECTANGULAR = 2; // 0x2
     field public static final int SCREEN_SHAPE_ROUND = 1; // 0x1
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index 3712fd2..dcc411b 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -196,7 +196,6 @@
     method public androidx.wear.watchface.WatchFace.Builder setPreviewReferenceTimeMillis(long previewReferenceTimeMillis);
     method public androidx.wear.watchface.WatchFace.Builder setWear2AccentColor(@ColorInt int accentColor);
     method public androidx.wear.watchface.WatchFace.Builder setWear2AcceptsTapEvents(boolean acceptsTapEvents);
-    method public androidx.wear.watchface.WatchFace.Builder setWear2ShowUnreadCountIndicator(boolean showUnreadCountIndicator);
     method public androidx.wear.watchface.WatchFace.Builder setWear2StatusBarGravity(int statusBarGravity);
     method public androidx.wear.watchface.WatchFace.Builder setWear2ViewProtectionMode(int viewProtectionMode);
   }
@@ -218,13 +217,11 @@
   }
 
   public final class WatchState {
-    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> inAirplaneMode, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isConnectedToCompanion, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isGpsActive, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isKeyguardLocked, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isInTheaterMode, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, androidx.wear.watchface.ObservableWatchData<java.lang.Integer> notificationCount, androidx.wear.watchface.ObservableWatchData<java.lang.Integer> unreadNotificationCount, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape);
+    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape);
     method public boolean getHasBurnInProtection();
     method public boolean getHasLowBitAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getNotificationCount();
     method public int getScreenShape();
-    method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getUnreadNotificationCount();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
     property public final boolean hasBurnInProtection;
@@ -232,9 +229,7 @@
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible;
-    property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> notificationCount;
     property public final int screenShape;
-    property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> unreadNotificationCount;
     field public static final androidx.wear.watchface.WatchState.Companion Companion;
     field public static final int SCREEN_SHAPE_RECTANGULAR = 2; // 0x2
     field public static final int SCREEN_SHAPE_ROUND = 1; // 0x1
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index 05ea23d..bbae9a3 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -140,18 +140,10 @@
     method public androidx.wear.watchface.WatchState asWatchState();
     method public boolean getHasBurnInProtection();
     method public boolean getHasLowBitAmbient();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> getInAirplaneMode();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> getNotificationCount();
     method public int getScreenShape();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> getUnreadNotificationCount();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isAmbient();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isCharging();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isConnectedToCompanion();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isGpsActive();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isInTheaterMode();
-    method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isKeyguardLocked();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isVisible();
     method public void setHasBurnInProtection(boolean p);
     method public void setHasLowBitAmbient(boolean p);
@@ -159,19 +151,11 @@
     method public void setScreenShape(int p);
     property public final boolean hasBurnInProtection;
     property public final boolean hasLowBitAmbient;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> inAirplaneMode;
     property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> interruptionFilter;
     property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isCharging;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isConnectedToCompanion;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isGpsActive;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isInTheaterMode;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isKeyguardLocked;
     property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isVisible;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> notificationCount;
     property public final int screenShape;
-    property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> unreadNotificationCount;
   }
 
   public class ObservableWatchData<T> {
@@ -237,7 +221,6 @@
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.wear.watchface.WatchFace.Builder setSystemTimeProvider(androidx.wear.watchface.WatchFace.SystemTimeProvider systemTimeProvider);
     method public androidx.wear.watchface.WatchFace.Builder setWear2AccentColor(@ColorInt int accentColor);
     method public androidx.wear.watchface.WatchFace.Builder setWear2AcceptsTapEvents(boolean acceptsTapEvents);
-    method public androidx.wear.watchface.WatchFace.Builder setWear2ShowUnreadCountIndicator(boolean showUnreadCountIndicator);
     method public androidx.wear.watchface.WatchFace.Builder setWear2StatusBarGravity(int statusBarGravity);
     method public androidx.wear.watchface.WatchFace.Builder setWear2ViewProtectionMode(int viewProtectionMode);
   }
@@ -275,13 +258,11 @@
   }
 
   public final class WatchState {
-    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> inAirplaneMode, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isConnectedToCompanion, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isGpsActive, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isKeyguardLocked, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isInTheaterMode, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, androidx.wear.watchface.ObservableWatchData<java.lang.Integer> notificationCount, androidx.wear.watchface.ObservableWatchData<java.lang.Integer> unreadNotificationCount, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape);
+    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape);
     method public boolean getHasBurnInProtection();
     method public boolean getHasLowBitAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getNotificationCount();
     method public int getScreenShape();
-    method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getUnreadNotificationCount();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
     property public final boolean hasBurnInProtection;
@@ -289,9 +270,7 @@
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible;
-    property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> notificationCount;
     property public final int screenShape;
-    property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> unreadNotificationCount;
     field public static final androidx.wear.watchface.WatchState.Companion Companion;
     field public static final int SCREEN_SHAPE_RECTANGULAR = 2; // 0x2
     field public static final int SCREEN_SHAPE_ROUND = 1; // 0x1
diff --git a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
index 3584b80..78f1a23 100644
--- a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
+++ b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
@@ -142,7 +142,7 @@
                     false,
                     DeviceConfig.SCREEN_SHAPE_ROUND
                 ),
-                SystemState(false, 0, 0, 0),
+                SystemState(false, 0),
                 null,
                 null
             )
@@ -173,7 +173,7 @@
                     false,
                     DeviceConfig.SCREEN_SHAPE_ROUND
                 ),
-                SystemState(false, 0, 0, 0),
+                SystemState(false, 0),
                 null,
                 null
             )
@@ -224,15 +224,7 @@
                 interactiveWatchFaceInstanceWCS.instanceId
             )!!.sysUiApi
 
-        interactiveWatchFaceInstanceSysUi.setSystemState(
-            SystemState(
-                ambient,
-                0,
-                0,
-                0
-            )
-        )
-
+        interactiveWatchFaceInstanceSysUi.setSystemState(SystemState(ambient, 0))
         interactiveWatchFaceInstanceSysUi.release()
     }
 
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index d20dd67..f2a30c5 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -178,8 +178,6 @@
 
         @ColorInt
         private var accentColor: Int = WatchFaceStyle.DEFAULT_ACCENT_COLOR
-        private var showUnreadCountIndicator: Boolean = false
-        private var hideNotificationIndicator: Boolean = false
         private var acceptsTapEvents: Boolean = true
         private var systemTimeProvider: SystemTimeProvider = object : SystemTimeProvider {
             override fun getSystemTimeMillis() = System.currentTimeMillis()
@@ -247,33 +245,6 @@
         }
 
         /**
-         * Sets whether to add an indicator of how many unread cards there are in the stream. The
-         * indicator will be displayed next to status icons (battery state, lack of connection).
-         *
-         * <p>Only has an impact on devices running Wear 2.x, on other devices this is a no-op and
-         * the functionality is replaced by... TODO(alexclarke): Design the replacement.
-         *
-         * @param showUnreadCountIndicator if true an indicator will be shown
-         */
-        public fun setWear2ShowUnreadCountIndicator(showUnreadCountIndicator: Boolean): Builder =
-            apply { this.showUnreadCountIndicator = showUnreadCountIndicator }
-
-        /**
-         * Sets whether to hide the dot indicator that is displayed at the bottom of the watch face
-         * if there are any unread notifications. The default value is false, but note that the
-         * dot will not be displayed if the numerical unread count indicator is being shown (i.e.
-         * if [getShowUnreadCountIndicator] is true).
-         *
-         * <p>Only has an impact on devices running Wear 2.x, on other devices this is a no-op and
-         * the functionality is replaced by... TODO(alexclarke): Design the replacement.
-         *
-         * @param hideNotificationIndicator if true an indicator will be hidden
-         * @hide
-         */
-        public fun setWear2HideNotificationIndicator(hideNotificationIndicator: Boolean): Builder =
-            apply { this.hideNotificationIndicator = hideNotificationIndicator }
-
-        /**
          * Sets whether this watchface accepts tap events. The default is false.
          *
          * <p>Only has an impact on devices running Wear 2.x, on other devices this is a no-op and
@@ -316,8 +287,8 @@
                     viewProtectionMode,
                     statusBarGravity,
                     accentColor,
-                    showUnreadCountIndicator,
-                    hideNotificationIndicator,
+                    false,
+                    false,
                     acceptsTapEvents
                 ),
                 componentName,
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index a4efd48..e4db227 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -63,7 +63,6 @@
 import androidx.wear.watchface.data.IdAndComplicationDetails
 import androidx.wear.watchface.data.DeviceConfig
 import androidx.wear.watchface.data.DeviceConfig.SCREEN_SHAPE_ROUND
-import androidx.wear.watchface.data.IndicatorState
 import androidx.wear.watchface.data.SystemState
 import androidx.wear.watchface.style.UserStyle
 import androidx.wear.watchface.style.data.UserStyleWireFormat
@@ -340,63 +339,9 @@
                 mutableWatchState.interruptionFilter.value = systemState.interruptionFilter
             }
 
-            if (firstSetSystemState ||
-                systemState.unreadCount != mutableWatchState.unreadNotificationCount.value
-            ) {
-                mutableWatchState.unreadNotificationCount.value = systemState.unreadCount
-            }
-
-            if (firstSetSystemState ||
-                systemState.notificationCount != mutableWatchState.notificationCount.value
-            ) {
-                mutableWatchState.notificationCount.value = systemState.notificationCount
-            }
-
             firstSetSystemState = false
         }
 
-        fun setIndicatorState(indicatorState: IndicatorState) {
-            if (firstIndicatorState ||
-                indicatorState.isCharging != mutableWatchState.isCharging.value
-            ) {
-                mutableWatchState.isCharging.value = indicatorState.isCharging
-            }
-
-            if (firstIndicatorState ||
-                indicatorState.inAirplaneMode != mutableWatchState.inAirplaneMode.value
-            ) {
-                mutableWatchState.inAirplaneMode.value = indicatorState.inAirplaneMode
-            }
-
-            if (firstIndicatorState ||
-                indicatorState.isConnectedToCompanion !=
-                mutableWatchState.isConnectedToCompanion.value
-            ) {
-                mutableWatchState.isConnectedToCompanion.value =
-                    indicatorState.isConnectedToCompanion
-            }
-
-            if (firstIndicatorState ||
-                indicatorState.inTheaterMode != mutableWatchState.isInTheaterMode.value
-            ) {
-                mutableWatchState.isInTheaterMode.value = indicatorState.inTheaterMode
-            }
-
-            if (firstIndicatorState ||
-                indicatorState.isGpsActive != mutableWatchState.isGpsActive.value
-            ) {
-                mutableWatchState.isGpsActive.value = indicatorState.isGpsActive
-            }
-
-            if (firstIndicatorState ||
-                indicatorState.isKeyguardLocked != mutableWatchState.isKeyguardLocked.value
-            ) {
-                mutableWatchState.isKeyguardLocked.value = indicatorState.isKeyguardLocked
-            }
-
-            firstIndicatorState = false
-        }
-
         @UiThread
         fun setUserStyle(userStyle: UserStyleWireFormat) {
             watchFace.onSetStyleInternal(
@@ -673,32 +618,10 @@
                     extras.getInt(
                         Constants.EXTRA_INTERRUPTION_FILTER,
                         mutableWatchState.interruptionFilter.getValueOr(0)
-                    ),
-                    extras.getInt(
-                        Constants.EXTRA_UNREAD_COUNT,
-                        mutableWatchState.unreadNotificationCount.getValueOr(0)
-                    ),
-                    extras.getInt(
-                        Constants.EXTRA_NOTIFICATION_COUNT,
-                        mutableWatchState.notificationCount.getValueOr(0)
                     )
                 )
             )
 
-            val statusBundle = extras.getBundle(Constants.EXTRA_INDICATOR_STATUS)
-            if (statusBundle != null) {
-                setIndicatorState(
-                    IndicatorState(
-                        statusBundle.getBoolean(Constants.STATUS_CHARGING),
-                        statusBundle.getBoolean(Constants.STATUS_AIRPLANE_MODE),
-                        statusBundle.getBoolean(Constants.STATUS_CONNECTED),
-                        statusBundle.getBoolean(Constants.STATUS_THEATER_MODE),
-                        statusBundle.getBoolean(Constants.STATUS_GPS_ACTIVE),
-                        statusBundle.getBoolean(Constants.STATUS_KEYGUARD_LOCKED)
-                    )
-                )
-            }
-
             pendingBackgroundAction = null
         }
 
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
index 12f8aff..669396a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
@@ -42,14 +42,6 @@
     public val isAmbient: ObservableWatchData<Boolean>,
 
     /**
-     * Whether or not the watch is in airplane mode. Only valid if
-     * [android.support.wearable.watchface.WatchFaceStyle.hideNotificationIndicator] is true.
-     *
-     * @hide
-     */
-    public val inAirplaneMode: ObservableWatchData<Boolean>,
-
-    /**
      * Whether or not we should conserve power due to a low battery which isn't charging. Only
      * valid if [android.support.wearable.watchface.WatchFaceStyle.hideNotificationIndicator] is
      * true.
@@ -58,55 +50,9 @@
      */
     public val isBatteryLowAndNotCharging: ObservableWatchData<Boolean>,
 
-    /**
-     * Whether or not the watch is charging. Only valid if
-     * [android.support.wearable.watchface.WatchFaceStyle.hideNotificationIndicator] is true.
-     *
-     * @hide
-     */
-    public val isCharging: ObservableWatchData<Boolean>,
-
-    /**
-     * Whether or not the watch is connected to the companion phone. Only valid if
-     * [android.support.wearable.watchface.WatchFaceStyle.hideNotificationIndicator] is true.
-     *
-     * @hide
-     */
-    public val isConnectedToCompanion: ObservableWatchData<Boolean>,
-
-    /**
-     * Whether or not GPS is active on the watch. Only valid if
-     * [android.support.wearable.watchface.WatchFaceStyle.hideNotificationIndicator] is true.
-     *
-     * @hide
-     */
-    public val isGpsActive: ObservableWatchData<Boolean>,
-
-    /**
-     * Whether or not the watch's keyguard (lock screen) is locked. Only valid if
-     * [android.support.wearable.watchface.WatchFaceStyle.hideNotificationIndicator] is true.
-     *
-     * @hide
-     */
-    public val isKeyguardLocked: ObservableWatchData<Boolean>,
-
-    /**
-     * Whether or not the watch is in theater mode. Only valid if
-     * [android.support.wearable.watchface.WatchFaceStyle.hideNotificationIndicator] is true.
-     *
-     * @hide
-     */
-    public val isInTheaterMode: ObservableWatchData<Boolean>,
-
     /** Whether or not the watch face is visible. */
     public val isVisible: ObservableWatchData<Boolean>,
 
-    /** The total number of notification cards in the stream. */
-    public val notificationCount: ObservableWatchData<Int>,
-
-    /** The total number of unread notification cards in the stream. */
-    public val unreadNotificationCount: ObservableWatchData<Int>,
-
     /** Whether or not the watch hardware supports low bit ambient support. */
     public val hasLowBitAmbient: Boolean,
 
@@ -134,20 +80,9 @@
 public class MutableWatchState {
     public var interruptionFilter: MutableObservableWatchData<Int> = MutableObservableWatchData()
     public val isAmbient: MutableObservableWatchData<Boolean> = MutableObservableWatchData()
-    public val inAirplaneMode: MutableObservableWatchData<Boolean> = MutableObservableWatchData()
     public val isBatteryLowAndNotCharging: MutableObservableWatchData<Boolean> =
         MutableObservableWatchData()
-    public val isCharging: MutableObservableWatchData<Boolean> = MutableObservableWatchData()
-    public val isConnectedToCompanion: MutableObservableWatchData<Boolean> =
-        MutableObservableWatchData()
-    public val isGpsActive: MutableObservableWatchData<Boolean> = MutableObservableWatchData()
-    public val isKeyguardLocked: MutableObservableWatchData<Boolean> =
-        MutableObservableWatchData()
-    public val isInTheaterMode: MutableObservableWatchData<Boolean> = MutableObservableWatchData()
     public val isVisible: MutableObservableWatchData<Boolean> = MutableObservableWatchData()
-    public val notificationCount: MutableObservableWatchData<Int> = MutableObservableWatchData()
-    public val unreadNotificationCount: MutableObservableWatchData<Int> =
-        MutableObservableWatchData()
     public var hasLowBitAmbient: Boolean = false
     public var hasBurnInProtection: Boolean = false
     public var screenShape: Int = 0
@@ -155,16 +90,8 @@
     public fun asWatchState(): WatchState = WatchState(
         interruptionFilter = interruptionFilter,
         isAmbient = isAmbient,
-        inAirplaneMode = inAirplaneMode,
         isBatteryLowAndNotCharging = isBatteryLowAndNotCharging,
-        isCharging = isCharging,
-        isConnectedToCompanion = isConnectedToCompanion,
-        isGpsActive = isGpsActive,
-        isKeyguardLocked = isKeyguardLocked,
-        isInTheaterMode = isInTheaterMode,
         isVisible = isVisible,
-        notificationCount = notificationCount,
-        unreadNotificationCount = unreadNotificationCount,
         hasLowBitAmbient = hasLowBitAmbient,
         hasBurnInProtection = hasBurnInProtection,
         screenShape = screenShape
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index ef41116..7b15c37 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -61,12 +61,10 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
 import org.mockito.Mockito.validateMockitoUsage
 import org.mockito.Mockito.verify
 import org.robolectric.annotation.Config
@@ -935,7 +933,7 @@
                     false,
                     DeviceConfig.SCREEN_SHAPE_ROUND
                 ),
-                SystemState(false, 0, 0, 0),
+                SystemState(false, 0),
                 UserStyle(
                     hashMapOf(
                         colorStyleCategory to blueStyleOption,
@@ -970,7 +968,7 @@
                     false,
                     DeviceConfig.SCREEN_SHAPE_ROUND
                 ),
-                SystemState(false, 0, 0, 0),
+                SystemState(false, 0),
                 UserStyle(hashMapOf(watchHandStyleCategory to badStyleOption)).toWireFormat(),
                 null
             )
@@ -981,68 +979,6 @@
     }
 
     @Test
-    fun maybeUpdateStatus_issuesCorrectApiCalls() {
-        initEngine(WatchFaceType.ANALOG, emptyList(), emptyList())
-
-        val bundle = Bundle().apply {
-            putBoolean(Constants.STATUS_CHARGING, true)
-            putBoolean(Constants.STATUS_AIRPLANE_MODE, false)
-            putBoolean(Constants.STATUS_CONNECTED, true)
-            putBoolean(Constants.STATUS_THEATER_MODE, false)
-            putBoolean(Constants.STATUS_GPS_ACTIVE, true)
-            putBoolean(Constants.STATUS_KEYGUARD_LOCKED, false)
-        }
-
-        val isChargingObserver = mock<Observer<Boolean>>()
-        val inAirplaneModeObserver = mock<Observer<Boolean>>()
-        val isConnectedToCompanionObserver = mock<Observer<Boolean>>()
-        val isInTheaterModeObserver = mock<Observer<Boolean>>()
-        val isGpsActiveObserver = mock<Observer<Boolean>>()
-        val isKeyguardLockedObserver = mock<Observer<Boolean>>()
-        watchState.isCharging.addObserver(isChargingObserver)
-        watchState.inAirplaneMode.addObserver(inAirplaneModeObserver)
-        watchState.isConnectedToCompanion.addObserver(isConnectedToCompanionObserver)
-        watchState.isInTheaterMode.addObserver(isInTheaterModeObserver)
-        watchState.isGpsActive.addObserver(isGpsActiveObserver)
-        watchState.isKeyguardLocked.addObserver(isKeyguardLockedObserver)
-
-        // Every indicator onXyz method should be called upon the initial update.
-        engineWrapper.onBackgroundAction(
-            Bundle().apply {
-                putBundle(Constants.EXTRA_INDICATOR_STATUS, bundle)
-            }
-        )
-
-        verify(isChargingObserver).onChanged(true)
-        verify(inAirplaneModeObserver).onChanged(false)
-        verify(isConnectedToCompanionObserver).onChanged(true)
-        verify(isInTheaterModeObserver).onChanged(false)
-        verify(isGpsActiveObserver).onChanged(true)
-        verify(isKeyguardLockedObserver).onChanged(false)
-
-        reset(isChargingObserver)
-        reset(inAirplaneModeObserver)
-        reset(isConnectedToCompanionObserver)
-        reset(isInTheaterModeObserver)
-        reset(isGpsActiveObserver)
-        reset(isKeyguardLockedObserver)
-
-        // Check only the modified setIsCharging state leads to a call.
-        bundle.putBoolean(Constants.STATUS_CHARGING, false)
-        engineWrapper.onBackgroundAction(
-            Bundle().apply {
-                putBundle(Constants.EXTRA_INDICATOR_STATUS, bundle)
-            }
-        )
-        verify(isChargingObserver).onChanged(false)
-        verify(inAirplaneModeObserver, times(0)).onChanged(anyBoolean())
-        verify(isConnectedToCompanionObserver, times(0)).onChanged(anyBoolean())
-        verify(isInTheaterModeObserver, times(0)).onChanged(anyBoolean())
-        verify(isGpsActiveObserver, times(0)).onChanged(anyBoolean())
-        verify(isKeyguardLockedObserver, times(0)).onChanged(anyBoolean())
-    }
-
-    @Test
     fun wear2ImmutablePropertiesSetCorrectly() {
         initEngine(WatchFaceType.ANALOG, emptyList(), emptyList(), 2, true, false)
 
@@ -1071,7 +1007,7 @@
                     false,
                     DeviceConfig.SCREEN_SHAPE_RECTANGULAR
                 ),
-                SystemState(false, 0, 0, 0),
+                SystemState(false, 0),
                 UserStyle(hashMapOf(watchHandStyleCategory to badStyleOption)).toWireFormat(),
                 null
             )