Merge "Investigating KSP Type Annotation possibility for Room support." into androidx-main
diff --git a/benchmark/benchmark-macro-junit4/build.gradle b/benchmark/benchmark-macro-junit4/build.gradle
index 5fa8211..75bd5ec 100644
--- a/benchmark/benchmark-macro-junit4/build.gradle
+++ b/benchmark/benchmark-macro-junit4/build.gradle
@@ -36,10 +36,10 @@
     api(libs.kotlinStdlib)
     api("androidx.annotation:annotation:1.1.0")
     api(project(":benchmark:benchmark-macro"))
+    api("androidx.test.uiautomator:uiautomator:2.2.0")
     implementation(project(":benchmark:benchmark-common"))
     implementation("androidx.test:rules:1.5.0")
     implementation("androidx.test:runner:1.5.0")
-    implementation("androidx.test.uiautomator:uiautomator:2.2.0")
 
     androidTestImplementation(project(":internal-testutils-ktx"))
     androidTestImplementation(libs.testExtJunit)
diff --git a/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt b/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
index 3d8531a..f53eb4f 100644
--- a/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
@@ -349,9 +349,6 @@
     <option name="config-descriptor:metadata" key="applicationId" value="com.androidx.placeholder.Placeholder" />
     <option name="wifi:disable" value="true" />
     <option name="instrumentation-arg" key="notAnnotation" value="androidx.test.filters.FlakyTest" />
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-    <option name="run-command" value="cmd package compile -f -m speed com.androidx.placeholder.Placeholder" />
-    </target_preparer>
     <option name="instrumentation-arg" key="androidx.benchmark.output.enable" value="true" />
     <option name="instrumentation-arg" key="listener" value="androidx.benchmark.junit4.InstrumentationResultsRunListener" />
     <include name="google/unbundled/common/setup" />
@@ -360,6 +357,9 @@
     <option name="install-arg" value="-t" />
     <option name="test-file-name" value="placeholder.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+    <option name="run-command" value="cmd package compile -f -m speed com.androidx.placeholder.Placeholder" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
     <option name="runner" value="com.example.Runner"/>
     <option name="package" value="com.androidx.placeholder.Placeholder" />
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
index e33eaf4..a3c6691 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
@@ -92,7 +92,6 @@
             .append(WIFI_DISABLE_OPTION)
             .append(FLAKY_TEST_OPTION)
         if (isBenchmark) {
-            sb.append(benchmarkPostInstallCommandOption(applicationId))
             if (isPostsubmit) {
                 sb.append(BENCHMARK_POSTSUBMIT_OPTIONS)
             } else {
@@ -105,7 +104,11 @@
         if (!appApkName.isNullOrEmpty())
             sb.append(APK_INSTALL_OPTION.replace("APK_NAME", appApkName!!))
         sb.append(TARGET_PREPARER_CLOSE)
-            .append(TEST_BLOCK_OPEN)
+        // Post install commands after SuiteApkInstaller is declared
+        if (isBenchmark) {
+            sb.append(benchmarkPostInstallCommandOption(applicationId))
+        }
+        sb.append(TEST_BLOCK_OPEN)
             .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
             .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
             .append(TEST_BLOCK_CLOSE)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt
index 56384dd..10a9cc1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/OwnersService.kt
@@ -38,7 +38,13 @@
     @Input
     fun getSerialized(): String {
         val gson = GsonBuilder().setPrettyPrinting().create()
-        return gson.toJson(testModules.associateBy { it.name })
+        // media service/client tests are created from multiple projects, so we get multiple
+        // entries with the same TestModule.name. This code merges all the TestModule.path entries
+        // across the test modules with the same name.
+        val data = testModules.groupBy { it.name }.map {
+            TestModule(name = it.key, path = it.value.flatMap { module -> module.path })
+        }.associateBy { it.name }
+        return gson.toJson(data)
     }
 
     @TaskAction
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index abc2099..ce67a56 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -60,8 +60,7 @@
 ) {
     val xmlName = "${path.asFilenamePrefix()}$variantName.xml"
     val jsonName = "_${path.asFilenamePrefix()}$variantName.json"
-    rootProject.tasks.named("createModuleInfo").configure {
-        it as ModuleInfoGenerator
+    rootProject.tasks.named<ModuleInfoGenerator>("createModuleInfo").configure {
         it.testModules.add(
             TestModule(
                 name = xmlName,
@@ -284,10 +283,57 @@
 ) {
     val mediaPrefix = getMediaConfigTaskPrefix(isMedia2)
     val mediaTask = getOrCreateMediaTestConfigTask(this, isMedia2)
+
+    fun getJsonName(clientToT: Boolean, serviceToT: Boolean, clientTests: Boolean): String {
+        return "_${mediaPrefix}Client${
+            if (clientToT) "ToT" else "Previous"
+        }Service${
+            if (serviceToT) "ToT" else "Previous"
+        }${
+            if (clientTests) "Client" else "Service"
+        }Tests$variantName.json"
+    }
+
+    fun ModuleInfoGenerator.addTestModule(clientToT: Boolean, serviceToT: Boolean) {
+        // We don't test the combination of previous versions of service and client as that is not
+        // useful data. We always want at least one tip of tree project.
+        if (!clientToT && !serviceToT) return
+        testModules.add(
+            TestModule(
+                name = getJsonName(
+                    clientToT = clientToT,
+                    serviceToT = serviceToT,
+                    clientTests = true
+                ),
+                path = listOf(projectDir.toRelativeString(getSupportRootFolder()))
+            )
+        )
+        testModules.add(
+            TestModule(
+                name = getJsonName(
+                    clientToT = clientToT,
+                    serviceToT = serviceToT,
+                    clientTests = false
+                ),
+                path = listOf(projectDir.toRelativeString(getSupportRootFolder()))
+            )
+        )
+    }
+    val isClient = this.name.contains("client")
+    val isPrevious = this.name.contains("previous")
+
+    rootProject.tasks.named<ModuleInfoGenerator>("createModuleInfo").configure {
+        if (isClient) {
+            it.addTestModule(clientToT = !isPrevious, serviceToT = false)
+            it.addTestModule(clientToT = !isPrevious, serviceToT = true)
+        } else {
+            it.addTestModule(clientToT = true, serviceToT = !isPrevious)
+            it.addTestModule(clientToT = false, serviceToT = !isPrevious)
+        }
+    }
     mediaTask.configure {
-        it as GenerateMediaTestConfigurationTask
-        if (this.name.contains("client")) {
-            if (this.name.contains("previous")) {
+        if (isClient) {
+            if (isPrevious) {
                 it.clientPreviousFolder.set(artifacts.get(SingleArtifact.APK))
                 it.clientPreviousLoader.set(artifacts.getBuiltArtifactsLoader())
             } else {
@@ -295,7 +341,7 @@
                 it.clientToTLoader.set(artifacts.getBuiltArtifactsLoader())
             }
         } else {
-            if (this.name.contains("previous")) {
+            if (isPrevious) {
                 it.servicePreviousFolder.set(artifacts.get(SingleArtifact.APK))
                 it.servicePreviousLoader.set(artifacts.getBuiltArtifactsLoader())
             } else {
@@ -305,32 +351,32 @@
         }
         it.jsonClientPreviousServiceToTClientTests.set(
             getFileInTestConfigDirectory(
-                "_${mediaPrefix}ClientPreviousServiceToTClientTests$variantName.json"
+                getJsonName(clientToT = false, serviceToT = true, clientTests = true)
             )
         )
         it.jsonClientPreviousServiceToTServiceTests.set(
             getFileInTestConfigDirectory(
-                "_${mediaPrefix}ClientPreviousServiceToTServiceTests$variantName.json"
+                getJsonName(clientToT = false, serviceToT = true, clientTests = false)
             )
         )
         it.jsonClientToTServicePreviousClientTests.set(
             getFileInTestConfigDirectory(
-                "_${mediaPrefix}ClientToTServicePreviousClientTests$variantName.json"
+                getJsonName(clientToT = true, serviceToT = false, clientTests = true)
             )
         )
         it.jsonClientToTServicePreviousServiceTests.set(
             getFileInTestConfigDirectory(
-                "_${mediaPrefix}ClientToTServicePreviousServiceTests$variantName.json"
+                getJsonName(clientToT = true, serviceToT = false, clientTests = false)
             )
         )
         it.jsonClientToTServiceToTClientTests.set(
             getFileInTestConfigDirectory(
-                "_${mediaPrefix}ClientToTServiceToTClientTests$variantName.json"
+                getJsonName(clientToT = true, serviceToT = true, clientTests = true)
             )
         )
         it.jsonClientToTServiceToTServiceTests.set(
             getFileInTestConfigDirectory(
-                "_${mediaPrefix}ClientToTServiceToTServiceTests$variantName.json"
+                getJsonName(clientToT = true, serviceToT = true, clientTests = false)
             )
         )
         it.totClientApk.set(getFileInTestConfigDirectory("${mediaPrefix}ClientToT$variantName.apk"))
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt
index dcc13ab..09bf890 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/IntrinsicTest.kt
@@ -49,6 +49,76 @@
 @RunWith(AndroidJUnit4::class)
 class IntrinsicTest : LayoutTest() {
     @Test
+    fun testMaxIntrinsic_HandleNegative() = with(density) {
+        val positionedLatch = CountDownLatch(2)
+        val size = Ref<IntSize>()
+        val position = Ref<Offset>()
+        val sizeTwo = Ref<IntSize>()
+        val positionTwo = Ref<Offset>()
+        val measurePolicy = object : MeasurePolicy {
+            override fun MeasureScope.measure(
+                measurables: List<Measurable>,
+                constraints: Constraints
+            ): MeasureResult {
+                return layout(0, 0) {}
+            }
+
+            override fun IntrinsicMeasureScope.minIntrinsicHeight(
+                measurables: List<IntrinsicMeasurable>,
+                width: Int
+            ): Int {
+                return -1
+            }
+
+            override fun IntrinsicMeasureScope.minIntrinsicWidth(
+                measurables: List<IntrinsicMeasurable>,
+                height: Int
+            ): Int {
+                return -1
+            }
+
+            override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+                measurables: List<IntrinsicMeasurable>,
+                width: Int
+            ): Int {
+                return -1
+            }
+
+            override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+                measurables: List<IntrinsicMeasurable>,
+                height: Int
+            ): Int {
+                return -1
+            }
+        }
+        show {
+            Column {
+                Layout(modifier = Modifier
+                    .width(IntrinsicSize.Min)
+                    .height(IntrinsicSize.Min)
+                    .saveLayoutInfo(
+                        size = size,
+                        position = position,
+                        positionedLatch = positionedLatch
+                    ), measurePolicy = measurePolicy)
+                Layout(modifier = Modifier
+                    .width(IntrinsicSize.Max)
+                    .height(IntrinsicSize.Max)
+                    .saveLayoutInfo(
+                        size = sizeTwo,
+                        position = positionTwo,
+                        positionedLatch = positionedLatch
+                    ), measurePolicy = measurePolicy)
+            }
+        }
+        assertTrue(positionedLatch.await(1, TimeUnit.SECONDS))
+        assertEquals(IntSize(0.dp.roundToPx(), 0.dp.roundToPx()), size.value)
+        assertEquals(IntSize(0.dp.roundToPx(), 0.dp.roundToPx()), sizeTwo.value)
+        assertEquals(Offset(0f, 0f), position.value)
+        assertEquals(Offset(0f, 0f), positionTwo.value)
+    }
+
+    @Test
     fun testMinIntrinsicWidth() = with(density) {
         val positionedLatch = CountDownLatch(2)
         val minIntrinsicWidthSize = Ref<IntSize>()
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
index fd13029..91c442b 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
@@ -3838,6 +3838,53 @@
     }
 
     @Test
+    fun testRow_withNoItems_hasCorrectIntrinsicMeasurements() = with(density) {
+        testIntrinsics(
+            @Composable {
+                Row(
+                    Modifier.width(IntrinsicSize.Max).height(IntrinsicSize.Max),
+                    horizontalArrangement = Arrangement.spacedBy(
+                        48.dp
+                    ),
+                ) { }
+            },
+            @Composable {
+                Row(
+                    Modifier.width(IntrinsicSize.Min).height(IntrinsicSize.Min),
+                    horizontalArrangement = Arrangement.spacedBy(
+                        48.dp
+                    ),
+                ) { }
+            },
+            @Composable {
+                Column(
+                    Modifier.width(IntrinsicSize.Max).height(IntrinsicSize.Max),
+                    verticalArrangement = Arrangement.spacedBy(
+                        48.dp
+                    ),
+                ) { }
+            },
+            @Composable {
+                Column(
+                    Modifier.width(IntrinsicSize.Min).height(IntrinsicSize.Min),
+                    verticalArrangement = Arrangement.spacedBy(
+                        48.dp
+                    ),
+                ) { }
+            },
+        ) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
+            // Min width.
+            assertEquals(0.toDp().roundToPx(), minIntrinsicWidth(0.toDp().roundToPx()))
+            // Min height.
+            assertEquals(0.toDp().roundToPx(), minIntrinsicHeight(0.toDp().roundToPx()))
+            // Max width.
+            assertEquals(0.toDp().roundToPx(), maxIntrinsicWidth(0.toDp().roundToPx()))
+            // Max height.
+            assertEquals(0.toDp().roundToPx(), maxIntrinsicHeight(0.toDp().roundToPx()))
+        }
+    }
+
+    @Test
     fun testRow_withWeightChildren_hasCorrectIntrinsicMeasurements() = with(density) {
         testIntrinsics(
             @Composable {
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
index b957ba4..37fe411 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
@@ -162,11 +162,12 @@
         measurable: Measurable,
         constraints: Constraints
     ): Constraints {
-        val measuredWidth = if (width == IntrinsicSize.Min) {
+        var measuredWidth = if (width == IntrinsicSize.Min) {
             measurable.minIntrinsicWidth(constraints.maxHeight)
         } else {
             measurable.maxIntrinsicWidth(constraints.maxHeight)
         }
+        if (measuredWidth < 0) { measuredWidth = 0 }
         return Constraints.fixedWidth(measuredWidth)
     }
 
@@ -217,11 +218,12 @@
         measurable: Measurable,
         constraints: Constraints
     ): Constraints {
-        val measuredHeight = if (height == IntrinsicSize.Min) {
+        var measuredHeight = if (height == IntrinsicSize.Min) {
             measurable.minIntrinsicHeight(constraints.maxWidth)
         } else {
             measurable.maxIntrinsicHeight(constraints.maxWidth)
         }
+        if (measuredHeight < 0) { measuredHeight = 0 }
         return Constraints.fixedHeight(measuredHeight)
     }
 
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index ce9028a..39edd26 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -557,6 +557,7 @@
     crossAxisAvailable: Int,
     mainAxisSpacing: Int
 ): Int {
+    if (children.isEmpty()) return 0
     var weightUnitSpace = 0
     var fixedSpace = 0
     var totalWeight = 0f
@@ -581,6 +582,7 @@
     mainAxisAvailable: Int,
     mainAxisSpacing: Int
 ): Int {
+    if (children.isEmpty()) return 0
     var fixedSpace = min((children.size - 1) * mainAxisSpacing, mainAxisAvailable)
     var crossAxisMax = 0
     var totalWeight = 0f
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
index f0f7f96..826dfbbc 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -61,6 +62,7 @@
 import com.google.common.truth.Truth.assertWithMessage
 import kotlin.math.abs
 import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -786,6 +788,25 @@
         }
     }
 
+    @MediumTest
+    @Test
+    fun testOverscrollModifierDrawsOnce() {
+        var drawCount = 0
+        rule.setContent {
+            Spacer(
+                modifier = Modifier.testTag(boxTag)
+                    .size(100.dp)
+                    .overscroll(ScrollableDefaults.overscrollEffect())
+                    .drawBehind {
+                        drawCount++
+                    }
+            )
+        }
+        rule.runOnIdle {
+            assertEquals(1, drawCount)
+        }
+    }
+
     @OptIn(ExperimentalTestApi::class)
     @ExperimentalFoundationApi
     @MediumTest
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt
index 64cb81d..767804ea 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldLongClickTest.kt
@@ -42,7 +42,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -123,9 +122,8 @@
         assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 7))
     }
 
-    @SdkSuppress(minSdkVersion = 23)
     @Test
-    fun longClickOnWhitespace_selectsNextWord() {
+    fun longClickOnWhitespace_doesNotSelectWhitespace() {
         val state = TextFieldState("abc def ghi")
         rule.setContent {
             BasicTextField2(
@@ -141,7 +139,8 @@
 
         rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
         rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selectionInChars).isEqualTo(TextRange(8, 11))
+        assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(7, 8))
+        assertThat(state.text.selectionInChars.collapsed).isFalse()
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
index 48fccdc..2da447e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
@@ -27,9 +27,10 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.neverEqualPolicy
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.geometry.Offset
@@ -123,7 +124,9 @@
         allEffects.fastForEach { it.color = overscrollConfig.glowColor.toArgb() }
     }
 
-    private val redrawSignal = mutableStateOf(Unit, neverEqualPolicy())
+    // TODO replace with mutableStateOf(Unit, neverEqualPolicy()) after b/291647821 is addressed
+    private var consumeCount = -1
+    private var invalidateCount by mutableIntStateOf(0)
 
     @VisibleForTesting
     internal var invalidationEnabled = true
@@ -349,7 +352,7 @@
             return
         }
         this.drawIntoCanvas {
-            redrawSignal.value // <-- value read to redraw if needed
+            consumeCount = invalidateCount // <-- value read to redraw if needed
             val canvas = it.nativeCanvas
             var needsInvalidate = false
             // each side workflow:
@@ -435,7 +438,9 @@
 
     private fun invalidateOverscroll() {
         if (invalidationEnabled) {
-            redrawSignal.value = Unit
+            if (consumeCount == invalidateCount) {
+                invalidateCount++
+            }
         }
     }
 
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
index 013adfe..b4bbc9c 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
@@ -437,11 +437,9 @@
     content: @Composable (PaddingValues) -> Unit
 ) {
     // b/278692145 Remove this once deprecated methods without density are removed
-    if (scaffoldState.bottomSheetState.density == null) {
-        val density = LocalDensity.current
-        SideEffect {
-            scaffoldState.bottomSheetState.density = density
-        }
+    val density = LocalDensity.current
+    SideEffect {
+        scaffoldState.bottomSheetState.density = density
     }
 
     val peekHeightPx = with(LocalDensity.current) { sheetPeekHeight.toPx() }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
index 6fbc761..bd51ba9 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
@@ -648,11 +648,9 @@
     content: @Composable () -> Unit
 ) {
     // b/278692145 Remove this once deprecated methods without density are removed
-    if (drawerState.density == null) {
-        val density = LocalDensity.current
-        SideEffect {
-            drawerState.density = density
-        }
+    val density = LocalDensity.current
+    SideEffect {
+        drawerState.density = density
     }
     val scope = rememberCoroutineScope()
 
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index dfe23a1..a07039a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -564,11 +564,9 @@
     content: @Composable () -> Unit
 ) {
     // b/278692145 Remove this once deprecated methods without density are removed
-    if (sheetState.density == null) {
-        val density = LocalDensity.current
-        SideEffect {
-            sheetState.density = density
-        }
+    val density = LocalDensity.current
+    SideEffect {
+        sheetState.density = density
     }
     val scope = rememberCoroutineScope()
     val orientation = Orientation.Vertical
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 227827f..615e6a4 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -530,7 +530,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class DismissState {
-    ctor public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+    ctor @Deprecated public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
     method public suspend Object? dismiss(androidx.compose.material3.DismissDirection direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.DismissValue getCurrentValue();
     method public androidx.compose.material3.DismissDirection? getDismissDirection();
@@ -548,7 +548,8 @@
   }
 
   public static final class DismissState.Companion {
-    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
@@ -1094,7 +1095,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
-    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
     method public float getActiveRangeEnd();
     method public float getActiveRangeStart();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
@@ -1286,8 +1287,12 @@
     property public final androidx.compose.foundation.shape.CornerBasedShape small;
   }
 
+  public final class SheetDefaultsKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.SheetState SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
-    ctor public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+    ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
     method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.SheetValue getCurrentValue();
     method public boolean getHasExpandedState();
@@ -1307,7 +1312,8 @@
   }
 
   public static final class SheetState.Companion {
-    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
@@ -1356,11 +1362,11 @@
 
   public final class SliderKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(androidx.compose.material3.RangeSliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track);
-    method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
+    method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional @IntRange(from=0L) int steps);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(androidx.compose.material3.SliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
-    method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
+    method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
   @androidx.compose.runtime.Stable public final class SliderPositions {
@@ -1372,7 +1378,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState {
-    ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+    ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
     method public int getSteps();
     method public float getValue();
@@ -1476,14 +1482,15 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissDefaults {
-    method public kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
-    property public final kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> FixedPositionalThreshold;
+    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
+    property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> fixedPositionalThreshold;
     field public static final androidx.compose.material3.SwipeToDismissDefaults INSTANCE;
   }
 
   public final class SwipeToDismissKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.DismissState DismissState(androidx.compose.material3.DismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
   }
 
   @androidx.compose.runtime.Immutable public final class SwitchColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 227827f..615e6a4 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -530,7 +530,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class DismissState {
-    ctor public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+    ctor @Deprecated public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
     method public suspend Object? dismiss(androidx.compose.material3.DismissDirection direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.DismissValue getCurrentValue();
     method public androidx.compose.material3.DismissDirection? getDismissDirection();
@@ -548,7 +548,8 @@
   }
 
   public static final class DismissState.Companion {
-    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
@@ -1094,7 +1095,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
-    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
     method public float getActiveRangeEnd();
     method public float getActiveRangeStart();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
@@ -1286,8 +1287,12 @@
     property public final androidx.compose.foundation.shape.CornerBasedShape small;
   }
 
+  public final class SheetDefaultsKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.SheetState SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
-    ctor public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+    ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
     method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.SheetValue getCurrentValue();
     method public boolean getHasExpandedState();
@@ -1307,7 +1312,8 @@
   }
 
   public static final class SheetState.Companion {
-    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
@@ -1356,11 +1362,11 @@
 
   public final class SliderKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(androidx.compose.material3.RangeSliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track);
-    method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
+    method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional @IntRange(from=0L) int steps);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(androidx.compose.material3.SliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
-    method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
+    method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
   @androidx.compose.runtime.Stable public final class SliderPositions {
@@ -1372,7 +1378,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState {
-    ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+    ctor public SliderState(optional float initialValue, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? initialOnValueChange, optional @IntRange(from=0L) int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
     method public int getSteps();
     method public float getValue();
@@ -1476,14 +1482,15 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissDefaults {
-    method public kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
-    property public final kotlin.jvm.functions.Function2<androidx.compose.ui.unit.Density,java.lang.Float,java.lang.Float> FixedPositionalThreshold;
+    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
+    property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> fixedPositionalThreshold;
     field public static final androidx.compose.material3.SwipeToDismissDefaults INSTANCE;
   }
 
   public final class SwipeToDismissKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.DismissState DismissState(androidx.compose.material3.DismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
   }
 
   @androidx.compose.runtime.Immutable public final class SwitchColors {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
index 71aa2de..6735b7d 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
@@ -253,6 +253,7 @@
             skipPartiallyExpanded = false,
             skipHiddenState = true,
             initialValue = SheetValue.PartiallyExpanded,
+            density = rule.density
         )
         rule.setContent {
             scope = rememberCoroutineScope()
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 8a34a90..a3fcc88 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -107,7 +107,7 @@
     @Test
     fun modalBottomSheet_isDismissedOnTapOutside() {
         var showBottomSheet by mutableStateOf(true)
-        val sheetState = SheetState(skipPartiallyExpanded = false)
+        val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
 
         rule.setContent {
             val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
@@ -294,7 +294,7 @@
     @Test
     fun modalBottomSheet_shortSheet_isDismissedOnBackPress() {
         var showBottomSheet by mutableStateOf(true)
-        val sheetState = SheetState(skipPartiallyExpanded = true)
+        val sheetState = SheetState(skipPartiallyExpanded = true, density = rule.density)
 
         rule.setContent {
             val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
@@ -333,7 +333,7 @@
     @Test
     fun modalBottomSheet_tallSheet_isDismissedOnBackPress() {
         var showBottomSheet by mutableStateOf(true)
-        val sheetState = SheetState(skipPartiallyExpanded = false)
+        val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
 
         rule.setContent {
             val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
@@ -565,7 +565,7 @@
     fun modalBottomSheet_missingAnchors_findsClosest() {
         val topTag = "ModalBottomSheetLayout"
         var showShortContent by mutableStateOf(false)
-        val sheetState = SheetState(skipPartiallyExpanded = false)
+        val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
         lateinit var scope: CoroutineScope
 
         rule.setContent {
@@ -809,6 +809,7 @@
         lateinit var scope: CoroutineScope
         val bottomSheetState = SheetState(
             skipPartiallyExpanded = true,
+            density = rule.density
         )
         rule.setContent {
             scope = rememberCoroutineScope()
@@ -1000,7 +1001,7 @@
 
     @Test
     fun modalBottomSheet_shortSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
-        val sheetState = SheetState(skipPartiallyExpanded = false)
+        val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
         var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
         lateinit var scope: CoroutineScope
         rule.setContent {
@@ -1039,7 +1040,7 @@
 
     @Test
     fun modalBottomSheet_tallSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
-        val sheetState = SheetState(skipPartiallyExpanded = false)
+        val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
         var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
         lateinit var scope: CoroutineScope
         rule.setContent {
@@ -1080,7 +1081,7 @@
     fun modalBottomSheet_callsOnDismissRequest_onNestedScrollFling() {
         var callCount by mutableStateOf(0)
         val expectedCallCount = 1
-        val sheetState = SheetState(skipPartiallyExpanded = true)
+        val sheetState = SheetState(skipPartiallyExpanded = true, density = rule.density)
 
         val nestedScrollDispatcher = NestedScrollDispatcher()
         val nestedScrollConnection = object : NestedScrollConnection {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
index 231f4d5..bafe835 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
@@ -20,6 +20,7 @@
 import android.os.Build
 import androidx.annotation.ColorRes
 import androidx.annotation.DoNotInline
+import androidx.annotation.FloatRange
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.colorspace.ColorSpaces
@@ -240,7 +241,7 @@
  * @param newLuminance 0 <= newLuminance <= 100; invalid values are corrected.
  */
 internal fun Color.setLuminance(
-    /*@FloatRange(from = 0.0, to = 100.0)*/
+    @FloatRange(from = 0.0, to = 100.0)
     newLuminance: Float
 ): Color {
     if ((newLuminance < 0.0001) or (newLuminance > 99.9999)) {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index 1dfdc5a..abd7fee 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -48,6 +48,7 @@
 import androidx.compose.runtime.CompositionContext
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -64,6 +65,7 @@
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.ViewRootForInspector
@@ -135,6 +137,11 @@
     windowInsets: WindowInsets = BottomSheetDefaults.windowInsets,
     content: @Composable ColumnScope.() -> Unit,
 ) {
+    // b/291735717 Remove this once deprecated methods without density are removed
+    val density = LocalDensity.current
+    SideEffect {
+        sheetState.density = density
+    }
     val scope = rememberCoroutineScope()
     val animateToDismiss: () -> Unit = {
         if (sheetState.swipeableState.confirmValueChange(Hidden)) {
@@ -247,8 +254,7 @@
                                             }
                                         } else if (hasPartiallyExpandedState) {
                                             collapse(collapseActionLabel) {
-                                                if (
-                                                    swipeableState.confirmValueChange(
+                                                if (swipeableState.confirmValueChange(
                                                         PartiallyExpanded
                                                     )
                                                 ) {
@@ -329,12 +335,12 @@
     screenHeight: Float,
     onDragStopped: CoroutineScope.(velocity: Float) -> Unit,
 ) = draggable(
-        state = sheetState.swipeableState.swipeDraggableState,
-        orientation = Orientation.Vertical,
-        enabled = sheetState.isVisible,
-        startDragImmediately = sheetState.swipeableState.isAnimationRunning,
-        onDragStopped = onDragStopped
-    )
+    state = sheetState.swipeableState.swipeDraggableState,
+    orientation = Orientation.Vertical,
+    enabled = sheetState.isVisible,
+    startDragImmediately = sheetState.swipeableState.isAnimationRunning,
+    onDragStopped = onDragStopped
+)
     .swipeAnchors(
         state = sheetState.swipeableState,
         anchorChangeHandler = anchorChangeHandler,
@@ -347,6 +353,7 @@
                 sheetState.skipPartiallyExpanded -> null
                 else -> screenHeight / 2f
             }
+
             Expanded -> if (sheetSize.height != 0) {
                 max(0f, screenHeight - sheetSize.height)
             } else null
@@ -479,8 +486,8 @@
             // Flags specific to modal bottom sheet.
             flags = flags and (
                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or
-                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-            ).inv()
+                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                ).inv()
 
             flags = flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
         }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
index 43e8c1a..0e30d0c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
@@ -28,6 +28,7 @@
 import androidx.compose.material3.SheetValue.Hidden
 import androidx.compose.material3.SheetValue.PartiallyExpanded
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -326,6 +327,11 @@
     containerColor: Color,
     contentColor: Color,
 ) {
+    // b/291735717 Remove this once deprecated methods without density are removed
+    val density = LocalDensity.current
+    SideEffect {
+        sheetState.density = density
+    }
     SubcomposeLayout { constraints ->
         val layoutWidth = constraints.maxWidth
         val layoutHeight = constraints.maxHeight
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
index 5a6a3366..e59e502 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
@@ -38,8 +38,10 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
@@ -48,7 +50,37 @@
 /**
  * State of a sheet composable, such as [ModalBottomSheet]
  *
- * Contains states relating to it's swipe position as well as animations between state values.
+ * Contains states relating to its swipe position as well as animations between state values.
+ *
+ * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is large
+ * enough, should be skipped. If true, the sheet will always expand to the [Expanded] state and move
+ * to the [Hidden] state if available when hiding the sheet, either programmatically or by user
+ * interaction.
+ * @param initialValue The initial value of the state.
+ * @param density The density that this state can use to convert values to and from dp.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ * @param skipHiddenState Whether the hidden state should be skipped. If true, the sheet will always
+ * expand to the [Expanded] state and move to the [PartiallyExpanded] if available, either
+ * programmatically or by user interaction.
+ */
+@ExperimentalMaterial3Api
+@Suppress("Deprecation")
+fun SheetState(
+    skipPartiallyExpanded: Boolean,
+    density: Density,
+    initialValue: SheetValue = Hidden,
+    confirmValueChange: (SheetValue) -> Boolean = { true },
+    skipHiddenState: Boolean = false,
+) = SheetState(
+    skipPartiallyExpanded, initialValue, confirmValueChange, skipHiddenState
+).also {
+    it.density = density
+}
+
+/**
+ * State of a sheet composable, such as [ModalBottomSheet]
+ *
+ * Contains states relating to its swipe position as well as animations between state values.
  *
  * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is large
  * enough, should be skipped. If true, the sheet will always expand to the [Expanded] state and move
@@ -62,7 +94,15 @@
  */
 @Stable
 @ExperimentalMaterial3Api
-class SheetState(
+class SheetState @Deprecated(
+    message = "This constructor is deprecated. " +
+        "Please use the constructor that provides a [Density]",
+    replaceWith = ReplaceWith(
+        "SheetState(" +
+            "skipPartiallyExpanded, LocalDensity.current, initialValue, " +
+            "confirmValueChange, skipHiddenState)"
+    )
+) constructor(
     internal val skipPartiallyExpanded: Boolean,
     initialValue: SheetValue = Hidden,
     confirmValueChange: (SheetValue) -> Boolean = { true },
@@ -237,16 +277,46 @@
         initialValue = initialValue,
         animationSpec = SwipeableV2Defaults.AnimationSpec,
         confirmValueChange = confirmValueChange,
+        positionalThreshold = { with(requireDensity()) { 56.dp.toPx() } },
+        velocityThreshold = { with(requireDensity()) { 125.dp.toPx() } }
     )
 
     internal val offset: Float? get() = swipeableState.offset
 
+    internal var density: Density? = null
+    private fun requireDensity() = requireNotNull(density) {
+        "SheetState did not have a density attached. Are you using SheetState with " +
+            "BottomSheetScaffold or ModalBottomSheet component?"
+    }
+
     companion object {
         /**
          * The default [Saver] implementation for [SheetState].
          */
         fun Saver(
             skipPartiallyExpanded: Boolean,
+            confirmValueChange: (SheetValue) -> Boolean,
+            density: Density
+        ) = Saver<SheetState, SheetValue>(
+            save = { it.currentValue },
+            restore = { savedValue ->
+                SheetState(skipPartiallyExpanded, density, savedValue, confirmValueChange)
+            }
+        )
+
+        /**
+         * The default [Saver] implementation for [SheetState].
+         */
+        @Deprecated(
+            message = "This function is deprecated. Please use the overload where Density is" +
+                " provided.",
+            replaceWith = ReplaceWith(
+                "Saver(skipPartiallyExpanded, confirmValueChange, LocalDensity.current)"
+            )
+        )
+        @Suppress("Deprecation")
+        fun Saver(
+            skipPartiallyExpanded: Boolean,
             confirmValueChange: (SheetValue) -> Boolean
         ) = Saver<SheetState, SheetValue>(
             save = { it.currentValue },
@@ -287,17 +357,17 @@
     /** The default shape for bottom sheets in a [Hidden] state. */
     val HiddenShape: Shape
         @Composable get() =
-        SheetBottomTokens.DockedMinimizedContainerShape.value
+            SheetBottomTokens.DockedMinimizedContainerShape.value
 
     /** The default shape for a bottom sheets in [PartiallyExpanded] and [Expanded] states. */
     val ExpandedShape: Shape
         @Composable get() =
-        SheetBottomTokens.DockedContainerShape.value
+            SheetBottomTokens.DockedContainerShape.value
 
     /** The default container color for a bottom sheet. */
     val ContainerColor: Color
         @Composable get() =
-        SheetBottomTokens.DockedContainerColor.value
+            SheetBottomTokens.DockedContainerColor.value
 
     /** The default elevation for a bottom sheet. */
     val Elevation = SheetBottomTokens.DockedModalContainerElevation
@@ -305,7 +375,7 @@
     /** The default color of the scrim overlay for background content. */
     val ScrimColor: Color
         @Composable get() =
-        ScrimTokens.ContainerColor.value.copy(ScrimTokens.ContainerOpacity)
+            ScrimTokens.ContainerColor.value.copy(ScrimTokens.ContainerOpacity)
 
     /**
      * The default peek height used by [BottomSheetScaffold].
@@ -414,14 +484,23 @@
     initialValue: SheetValue = Hidden,
     skipHiddenState: Boolean = false,
 ): SheetState {
+
+    val density = LocalDensity.current
     return rememberSaveable(
         skipPartiallyExpanded, confirmValueChange,
         saver = SheetState.Saver(
             skipPartiallyExpanded = skipPartiallyExpanded,
-            confirmValueChange = confirmValueChange
+            confirmValueChange = confirmValueChange,
+            density = density
         )
     ) {
-        SheetState(skipPartiallyExpanded, initialValue, confirmValueChange, skipHiddenState)
+        SheetState(
+            skipPartiallyExpanded,
+            density,
+            initialValue,
+            confirmValueChange,
+            skipHiddenState
+        )
     }
 }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index a00e616..32ea286 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.material3
 
+import androidx.annotation.IntRange
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
@@ -153,7 +154,7 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
-    /*@IntRange(from = 0)*/
+    @IntRange(from = 0)
     steps: Int = 0,
     onValueChangeFinished: (() -> Unit)? = null,
     colors: SliderColors = SliderDefaults.colors(),
@@ -249,7 +250,7 @@
     onValueChangeFinished: (() -> Unit)? = null,
     colors: SliderColors = SliderDefaults.colors(),
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    /*@IntRange(from = 0)*/
+    @IntRange(from = 0)
     steps: Int = 0,
     thumb: @Composable (SliderState) -> Unit = {
         SliderDefaults.Thumb(
@@ -412,7 +413,7 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
-    /*@IntRange(from = 0)*/
+    @IntRange(from = 0)
     steps: Int = 0,
     onValueChangeFinished: (() -> Unit)? = null,
     colors: SliderColors = SliderDefaults.colors()
@@ -541,7 +542,7 @@
             rangeSliderState = rangeSliderState
         )
     },
-    /*@IntRange(from = 0)*/
+    @IntRange(from = 0)
     steps: Int = 0
 ) {
     val state = remember(
@@ -1789,7 +1790,7 @@
 class SliderState(
     initialValue: Float = 0f,
     initialOnValueChange: ((Float) -> Unit)? = null,
-    /*@IntRange(from = 0)*/
+    @IntRange(from = 0)
     val steps: Int = 0,
     val valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
     var onValueChangeFinished: (() -> Unit)? = null
@@ -1909,7 +1910,7 @@
     initialActiveRangeStart: Float = 0f,
     initialActiveRangeEnd: Float = 1f,
     initialOnValueChange: ((FloatRange) -> Unit)? = null,
-    /*@IntRange(from = 0)*/
+    @IntRange(from = 0)
     val steps: Int = 0,
     val valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
     var onValueChangeFinished: (() -> Unit)? = null,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
index bce5e38..4506e25 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
@@ -23,14 +23,17 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.material3.DismissDirection.EndToStart
 import androidx.compose.material3.DismissDirection.StartToEnd
+import androidx.compose.material3.DismissState.Companion.Saver
 import androidx.compose.material3.DismissValue.Default
 import androidx.compose.material3.DismissValue.DismissedToEnd
 import androidx.compose.material3.DismissValue.DismissedToStart
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
@@ -80,6 +83,7 @@
  * State of the [SwipeToDismiss] composable.
  *
  * @param initialValue The initial value of the state.
+ * @param density The density that this state can use to convert values to and from dp.
  * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
  * @param positionalThreshold The positional threshold to be used when calculating the target state
  * while a swipe is in progress and when settling after the swipe ends. This is the distance from
@@ -87,17 +91,49 @@
  * subtracted from/to the origin offset. It should always be a positive value.
  */
 @ExperimentalMaterial3Api
-class DismissState(
+@Suppress("Deprecation", "PrimitiveInLambda")
+fun DismissState(
+    initialValue: DismissValue,
+    density: Density,
+    confirmValueChange: (DismissValue) -> Boolean = { true },
+    positionalThreshold: (totalDistance: Float) -> Float
+) = DismissState(
+    initialValue = initialValue,
+    confirmValueChange = confirmValueChange,
+    positionalThreshold = positionalThreshold
+).also {
+    it.density = density
+}
+
+/**
+ * State of the [SwipeToDismiss] composable.
+ *
+ * @param initialValue The initial value of the state.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ * @param positionalThreshold The positional threshold to be used when calculating the target state
+ * while a swipe is in progress and when settling after the swipe ends. This is the distance from
+ * the start of a transition. It will be, depending on the direction of the interaction, added or
+ * subtracted from/to the origin offset. It should always be a positive value.
+ */
+@Suppress("PrimitiveInLambda")
+@ExperimentalMaterial3Api
+class DismissState @Deprecated(
+    message = "This constructor is deprecated. " +
+        "Please use the constructor that provides a [Density]",
+    replaceWith = ReplaceWith(
+        "DismissState(" +
+            "initialValue, LocalDensity.current, confirmValueChange, positionalThreshold)"
+    )
+) constructor(
     initialValue: DismissValue,
     confirmValueChange: (DismissValue) -> Boolean = { true },
-    positionalThreshold: Density.(totalDistance: Float) -> Float =
-        SwipeToDismissDefaults.FixedPositionalThreshold,
+    positionalThreshold: (totalDistance: Float) -> Float
 ) {
     internal val swipeableState = SwipeableV2State(
         initialValue = initialValue,
         confirmValueChange = confirmValueChange,
         positionalThreshold = positionalThreshold,
-        velocityThreshold = DismissThreshold
+        velocityThreshold = { with(requireDensity()) { DismissThreshold.toPx() } }
     )
 
     internal val offset: Float? get() = swipeableState.offset
@@ -132,8 +168,10 @@
      * If the composable is settled at the default state, then this will be null. Use this to
      * change the background of the [SwipeToDismiss] if you want different actions on each side.
      */
-    val dismissDirection: DismissDirection? get() =
-        if (offset == 0f || offset == null) null else if (offset!! > 0f) StartToEnd else EndToStart
+    val dismissDirection: DismissDirection?
+        get() = if (offset == 0f || offset == null)
+            null
+        else if (offset!! > 0f) StartToEnd else EndToStart
 
     /**
      * Whether the component has been dismissed in the given [direction].
@@ -173,19 +211,52 @@
         swipeableState.animateTo(targetValue = targetValue)
     }
 
+    internal var density: Density? = null
+    private fun requireDensity() = requireNotNull(density) {
+        "DismissState did not have a density attached. Are you using DismissState with " +
+            "the SwipeToDismiss component?"
+    }
+
     companion object {
+
         /**
          * The default [Saver] implementation for [DismissState].
          */
         fun Saver(
             confirmValueChange: (DismissValue) -> Boolean,
-            positionalThreshold: Density.(totalDistance: Float) -> Float,
+            positionalThreshold: (totalDistance: Float) -> Float,
+            density: Density
         ) =
             Saver<DismissState, DismissValue>(
                 save = { it.currentValue },
                 restore = {
                     DismissState(
-                        it, confirmValueChange, positionalThreshold)
+                        it, density, confirmValueChange, positionalThreshold
+                    )
+                }
+            )
+
+        /**
+         * The default [Saver] implementation for [DismissState].
+         */
+        @Deprecated(
+            message = "This function is deprecated. Please use the overload where Density is" +
+                " provided.",
+            replaceWith = ReplaceWith(
+                "Saver(confirmValueChange, positionalThreshold, LocalDensity.current)"
+            )
+        )
+        @Suppress("Deprecation")
+        fun Saver(
+            confirmValueChange: (DismissValue) -> Boolean,
+            positionalThreshold: (totalDistance: Float) -> Float,
+        ) =
+            Saver<DismissState, DismissValue>(
+                save = { it.currentValue },
+                restore = {
+                    DismissState(
+                        it, confirmValueChange, positionalThreshold
+                    )
                 }
             )
     }
@@ -201,17 +272,24 @@
  * the start of a transition. It will be, depending on the direction of the interaction, added or
  * subtracted from/to the origin offset. It should always be a positive value.
  */
+@Suppress("PrimitiveInLambda")
 @Composable
 @ExperimentalMaterial3Api
 fun rememberDismissState(
     initialValue: DismissValue = Default,
     confirmValueChange: (DismissValue) -> Boolean = { true },
-    positionalThreshold: Density.(totalDistance: Float) -> Float =
-        SwipeToDismissDefaults.FixedPositionalThreshold,
+    positionalThreshold: (totalDistance: Float) -> Float =
+        SwipeToDismissDefaults.fixedPositionalThreshold,
 ): DismissState {
+    val density = LocalDensity.current
     return rememberSaveable(
-        saver = DismissState.Saver(confirmValueChange, positionalThreshold)) {
-        DismissState(initialValue, confirmValueChange, positionalThreshold)
+        saver = DismissState.Saver(
+            confirmValueChange = confirmValueChange,
+            density = density,
+            positionalThreshold = positionalThreshold
+        )
+    ) {
+        DismissState(initialValue, density, confirmValueChange, positionalThreshold)
     }
 }
 
@@ -236,6 +314,13 @@
     modifier: Modifier = Modifier,
     directions: Set<DismissDirection> = setOf(EndToStart, StartToEnd),
 ) {
+
+    // b/278692145 Remove this once deprecated methods without density are removed
+    val density = LocalDensity.current
+    SideEffect {
+        state.density = density
+    }
+
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
 
     Box(
@@ -257,23 +342,27 @@
                     Default -> 0f
                 }
             }
-        ) {
-            Row(
-                content = background,
-                modifier = Modifier.matchParentSize()
-            )
-            Row(
-                content = dismissContent,
-                modifier = Modifier.offset { IntOffset(state.requireOffset().roundToInt(), 0) }
-            )
-        }
+    ) {
+        Row(
+            content = background,
+            modifier = Modifier.matchParentSize()
+        )
+        Row(
+            content = dismissContent,
+            modifier = Modifier.offset { IntOffset(state.requireOffset().roundToInt(), 0) }
+        )
+    }
 }
 
 /** Contains default values for [SwipeToDismiss] and [DismissState]. */
+@Suppress("PrimitiveInLambda")
 @ExperimentalMaterial3Api
 object SwipeToDismissDefaults {
     /** Default positional threshold of 56.dp for [DismissState]. */
-    val FixedPositionalThreshold: Density.(totalDistance: Float) -> Float = { _ -> 56.dp.toPx() }
+    val fixedPositionalThreshold: (totalDistance: Float) -> Float
+        @Composable get() = with(LocalDensity.current) {
+            { 56.dp.toPx() }
+        }
 }
 
 private val DismissThreshold = 125.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt
index e135720..f06b0e3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Swipeable.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.material3
 
+import androidx.annotation.FloatRange
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.SpringSpec
@@ -431,7 +432,7 @@
 internal class SwipeProgress<T>(
     val from: T,
     val to: T,
-    /*@FloatRange(from = 0.0, to = 1.0)*/
+    @FloatRange(from = 0.0, to = 1.0)
     val fraction: Float
 ) {
     override fun equals(other: Any?): Boolean {
@@ -536,7 +537,7 @@
  * the new anchor. The target anchor is calculated based on the provided positional [thresholds].
  *
  * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
- * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * past these bounds, a resistance effect will bfe applied by default. The amount of resistance at
  * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
  *
  * @param T The type of the state.
@@ -650,7 +651,7 @@
 @Immutable
 @ExperimentalMaterial3Api
 internal data class FractionalThreshold(
-    /*@FloatRange(from = 0.0, to = 1.0)*/
+    @FloatRange(from = 0.0, to = 1.0)
     private val fraction: Float
 ) : ThresholdConfig {
     override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
@@ -682,11 +683,11 @@
  */
 @Immutable
 internal class ResistanceConfig(
-    /*@FloatRange(from = 0.0, fromInclusive = false)*/
+    @FloatRange(from = 0.0, fromInclusive = false)
     val basis: Float,
-    /*@FloatRange(from = 0.0)*/
+    @FloatRange(from = 0.0)
     val factorAtMin: Float = StandardResistanceFactor,
-    /*@FloatRange(from = 0.0)*/
+    @FloatRange(from = 0.0)
     val factorAtMax: Float = StandardResistanceFactor
 ) {
     fun computeResistance(overflow: Float): Float {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
index 8db36b9..dfead31 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.material3
 
+import androidx.annotation.FloatRange
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.animate
@@ -38,17 +39,8 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.LayoutModifier
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.layout.OnRemeasuredModifier
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import kotlin.math.abs
@@ -111,37 +103,27 @@
     possibleValues: Set<T>,
     anchorChangeHandler: AnchorChangeHandler<T>? = null,
     calculateAnchor: (value: T, layoutSize: IntSize) -> Float?,
-) = this.then(SwipeAnchorsModifier(
-    onDensityChanged = { state.density = it },
-    onSizeChanged = { layoutSize ->
-        val previousAnchors = state.anchors
-        val newAnchors = mutableMapOf<T, Float>()
-        possibleValues.forEach {
-            val anchorValue = calculateAnchor(it, layoutSize)
-            if (anchorValue != null) {
-                newAnchors[it] = anchorValue
-            }
+) = onSizeChanged { layoutSize ->
+    val previousAnchors = state.anchors
+    val newAnchors = mutableMapOf<T, Float>()
+    possibleValues.forEach {
+        val anchorValue = calculateAnchor(it, layoutSize)
+        if (anchorValue != null) {
+            newAnchors[it] = anchorValue
         }
-        if (previousAnchors != newAnchors) {
-            val previousTarget = state.targetValue
-            val stateRequiresCleanup = state.updateAnchors(newAnchors)
-            if (stateRequiresCleanup) {
-                anchorChangeHandler?.onAnchorsChanged(
-                    previousTarget,
-                    previousAnchors,
-                    newAnchors
-                )
-            }
-        }
-    },
-    inspectorInfo = debugInspectorInfo {
-        name = "swipeAnchors"
-        properties["state"] = state
-        properties["possibleValues"] = possibleValues
-        properties["anchorChangeHandler"] = anchorChangeHandler
-        properties["calculateAnchor"] = calculateAnchor
     }
-))
+    if (previousAnchors != newAnchors) {
+        val previousTarget = state.targetValue
+        val stateRequiresCleanup = state.updateAnchors(newAnchors)
+        if (stateRequiresCleanup) {
+            anchorChangeHandler?.onAnchorsChanged(
+                previousTarget,
+                previousAnchors,
+                newAnchors
+            )
+        }
+    }
+}
 
 /**
  * State of the [swipeableV2] modifier.
@@ -153,24 +135,23 @@
  * @param initialValue The initial value of the state.
  * @param animationSpec The default animation that will be used to animate to a new state.
  * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
- * @param positionalThreshold The positional threshold to be used when calculating the target state
- * while a swipe is in progress and when settling after the swipe ends. This is the distance from
- * the start of a transition. It will be, depending on the direction of the interaction, added or
- * subtracted from/to the origin offset. It should always be a positive value. See the
- * [fractionalPositionalThreshold] and [fixedPositionalThreshold] methods.
- * @param velocityThreshold The velocity threshold (in dp per second) that the end velocity has to
+ * @param positionalThreshold The positional threshold, in px, to be used when calculating the
+ * target state while a swipe is in progress and when settling after the swipe ends. This is the
+ * distance from the start of a transition. It will be, depending on the direction of the
+ * interaction, added or subtracted from/to the origin offset. It should always be a positive value.
+ * @param velocityThreshold The velocity threshold (in px per second) that the end velocity has to
  * exceed in order to animate to the next state, even if the [positionalThreshold] has not been
  * reached.
  */
+@Suppress("PrimitiveInLambda")
 @Stable
 @ExperimentalMaterial3Api
 internal class SwipeableV2State<T>(
     initialValue: T,
+    internal val positionalThreshold: (totalDistance: Float) -> Float,
+    internal val velocityThreshold: () -> Float,
     internal val animationSpec: AnimationSpec<Float> = SwipeableV2Defaults.AnimationSpec,
     internal val confirmValueChange: (newValue: T) -> Boolean = { true },
-    internal val positionalThreshold: Density.(totalDistance: Float) -> Float =
-        SwipeableV2Defaults.PositionalThreshold,
-    internal val velocityThreshold: Dp = SwipeableV2Defaults.VelocityThreshold,
 ) {
 
     private val swipeMutex = InternalMutatorMutex()
@@ -250,7 +231,7 @@
      * The fraction of the progress going from [currentValue] to [targetValue], within [0f..1f]
      * bounds.
      */
-    /*@FloatRange(from = 0f, to = 1f)*/
+    @get:FloatRange(from = 0.0, to = 1.0)
     val progress: Float by derivedStateOf {
         val a = anchors[currentValue] ?: 0f
         val b = anchors[targetValue] ?: 0f
@@ -287,8 +268,6 @@
 
     internal var anchors by mutableStateOf(emptyMap<T, Float>())
 
-    internal var density: Density? = null
-
     /**
      * Update the anchors.
      * If the previous set of anchors was empty, attempt to update the offset to match the initial
@@ -417,8 +396,7 @@
     ): T {
         val currentAnchors = anchors
         val currentAnchor = currentAnchors[currentValue]
-        val currentDensity = requireDensity()
-        val velocityThresholdPx = with(currentDensity) { velocityThreshold.toPx() }
+        val velocityThresholdPx = velocityThreshold()
         return if (currentAnchor == offset || currentAnchor == null) {
             currentValue
         } else if (currentAnchor < offset) {
@@ -428,7 +406,7 @@
             } else {
                 val upper = currentAnchors.closestAnchor(offset, true)
                 val distance = abs(currentAnchors.getValue(upper) - currentAnchor)
-                val relativeThreshold = abs(positionalThreshold(currentDensity, distance))
+                val relativeThreshold = abs(positionalThreshold(distance))
                 val absoluteThreshold = abs(currentAnchor + relativeThreshold)
                 if (offset < absoluteThreshold) currentValue else upper
             }
@@ -439,7 +417,7 @@
             } else {
                 val lower = currentAnchors.closestAnchor(offset, false)
                 val distance = abs(currentAnchor - currentAnchors.getValue(lower))
-                val relativeThreshold = abs(positionalThreshold(currentDensity, distance))
+                val relativeThreshold = abs(positionalThreshold(distance))
                 val absoluteThreshold = abs(currentAnchor - relativeThreshold)
                 if (offset < 0) {
                     // For negative offsets, larger absolute thresholds are closer to lower anchors
@@ -452,11 +430,6 @@
         }
     }
 
-    private fun requireDensity() = requireNotNull(density) {
-        "SwipeableState did not have a density attached. Are you using Modifier.swipeable with " +
-            "this=$this SwipeableState?"
-    }
-
     private suspend fun swipe(
         swipePriority: MutatePriority = MutatePriority.Default,
         action: suspend () -> Unit
@@ -490,8 +463,8 @@
         fun <T : Any> Saver(
             animationSpec: AnimationSpec<Float>,
             confirmValueChange: (T) -> Boolean,
-            positionalThreshold: Density.(distance: Float) -> Float,
-            velocityThreshold: Dp
+            positionalThreshold: (distance: Float) -> Float,
+            velocityThreshold: () -> Float
         ) = Saver<SwipeableV2State<T>, T>(
             save = { it.currentValue },
             restore = {
@@ -514,6 +487,7 @@
  * @param animationSpec The default animation that will be used to animate to a new value.
  * @param confirmValueChange Optional callback invoked to confirm or veto a pending value change.
  */
+@Suppress("PrimitiveInLambda")
 @Composable
 @ExperimentalMaterial3Api
 internal fun <T : Any> rememberSwipeableV2State(
@@ -521,51 +495,32 @@
     animationSpec: AnimationSpec<Float> = SwipeableV2Defaults.AnimationSpec,
     confirmValueChange: (newValue: T) -> Boolean = { true }
 ): SwipeableV2State<T> {
+    val positionalThreshold = SwipeableV2Defaults.positionalThreshold
+    val velocityThreshold = SwipeableV2Defaults.velocityThreshold
+
     return rememberSaveable(
-        initialValue, animationSpec, confirmValueChange,
+        initialValue, animationSpec, confirmValueChange, positionalThreshold, velocityThreshold,
         saver = SwipeableV2State.Saver(
             animationSpec = animationSpec,
             confirmValueChange = confirmValueChange,
-            positionalThreshold = SwipeableV2Defaults.PositionalThreshold,
-            velocityThreshold = SwipeableV2Defaults.VelocityThreshold
+            positionalThreshold = positionalThreshold,
+            velocityThreshold = velocityThreshold
         ),
     ) {
         SwipeableV2State(
             initialValue = initialValue,
             animationSpec = animationSpec,
             confirmValueChange = confirmValueChange,
-            positionalThreshold = SwipeableV2Defaults.PositionalThreshold,
-            velocityThreshold = SwipeableV2Defaults.VelocityThreshold
+            positionalThreshold = positionalThreshold,
+            velocityThreshold = velocityThreshold
         )
     }
 }
 
 /**
- * Expresses a fixed positional threshold of [threshold] dp. This will be the distance from an
- * anchor that needs to be reached for [SwipeableV2State] to settle to the next closest anchor.
- *
- * @see [fractionalPositionalThreshold] for a fractional positional threshold
- */
-@ExperimentalMaterial3Api
-internal fun fixedPositionalThreshold(threshold: Dp): Density.(distance: Float) -> Float = {
-    threshold.toPx()
-}
-
-/**
- * Expresses a relative positional threshold of the [fraction] of the distance to the closest anchor
- * in the current direction. This will be the distance from an anchor that needs to be reached for
- * [SwipeableV2State] to settle to the next closest anchor.
- *
- * @see [fixedPositionalThreshold] for a fixed positional threshold
- */
-@ExperimentalMaterial3Api
-internal fun fractionalPositionalThreshold(
-    fraction: Float
-): Density.(distance: Float) -> Float = { distance -> distance * fraction }
-
-/**
  * Contains useful defaults for [swipeableV2] and [SwipeableV2State].
  */
+@Suppress("PrimitiveInLambda")
 @Stable
 @ExperimentalMaterial3Api
 internal object SwipeableV2Defaults {
@@ -579,14 +534,17 @@
      * The default velocity threshold (1.8 dp per millisecond) used by [rememberSwipeableV2State].
      */
     @ExperimentalMaterial3Api
-    val VelocityThreshold: Dp = 125.dp
+    val velocityThreshold: () -> Float
+        @Composable get() = with(LocalDensity.current) { { 125.dp.toPx() } }
 
     /**
      * The default positional threshold (56 dp) used by [rememberSwipeableV2State]
      */
     @ExperimentalMaterial3Api
-    val PositionalThreshold: Density.(totalDistance: Float) -> Float =
-        fixedPositionalThreshold(56.dp)
+    val positionalThreshold: (totalDistance: Float) -> Float
+        @Composable get() = with(LocalDensity.current) {
+            { 56.dp.toPx() }
+        }
 
     /**
      * A [AnchorChangeHandler] implementation that attempts to reconcile an in-progress animation
@@ -646,37 +604,6 @@
     )
 }
 
-@Stable
-private class SwipeAnchorsModifier(
-    private val onDensityChanged: (density: Density) -> Unit,
-    private val onSizeChanged: (layoutSize: IntSize) -> Unit,
-    inspectorInfo: InspectorInfo.() -> Unit,
-) : LayoutModifier, OnRemeasuredModifier, InspectorValueInfo(inspectorInfo) {
-
-    private var lastDensity: Float = -1f
-    private var lastFontScale: Float = -1f
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        if (density != lastDensity || fontScale != lastFontScale) {
-            onDensityChanged(Density(density, fontScale))
-            lastDensity = density
-            lastFontScale = fontScale
-        }
-        val placeable = measurable.measure(constraints)
-        return layout(placeable.width, placeable.height) { placeable.place(0, 0) }
-    }
-
-    override fun onRemeasured(size: IntSize) {
-        onSizeChanged(size)
-    }
-
-    override fun toString() = "SwipeAnchorsModifierImpl(updateDensity=$onDensityChanged, " +
-        "onSizeChanged=$onSizeChanged)"
-}
-
 private fun <T> Map<T, Float>.closestAnchor(
     offset: Float = 0f,
     searchUpwards: Boolean = false
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
index 8d021e3..f99c430 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
@@ -543,6 +543,25 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testVectorStrokeWidth() {
+        val strokeWidth = mutableStateOf(100)
+        rule.setContent {
+            VectorStroke(strokeWidth = strokeWidth.value)
+        }
+        takeScreenShot(200).apply {
+            assertEquals(Color.Yellow.toArgb(), getPixel(100, 25))
+            assertEquals(Color.Blue.toArgb(), getPixel(100, 75))
+        }
+        rule.runOnUiThread { strokeWidth.value = 200 }
+        rule.waitForIdle()
+        takeScreenShot(200).apply {
+            assertEquals(Color.Yellow.toArgb(), getPixel(100, 25))
+            assertEquals(Color.Yellow.toArgb(), getPixel(100, 75))
+        }
+    }
+
     @Composable
     private fun VectorTint(
         size: Int = 200,
@@ -681,6 +700,47 @@
     }
 
     @Composable
+    private fun VectorStroke(
+        size: Int = 200,
+        strokeWidth: Int = 100,
+        minimumSize: Int = size,
+        alignment: Alignment = Alignment.Center
+    ) {
+        val sizePx = size.toFloat()
+        val sizeDp = (size / LocalDensity.current.density).dp
+        val strokeWidthPx = strokeWidth.toFloat()
+        val background = Modifier.paint(
+            rememberVectorPainter(
+                defaultWidth = sizeDp,
+                defaultHeight = sizeDp,
+                autoMirror = false
+            ) { _, _ ->
+                Path(
+                    pathData = PathData {
+                        lineTo(sizePx, 0.0f)
+                        lineTo(sizePx, sizePx)
+                        lineTo(0.0f, sizePx)
+                        close()
+                    },
+                    fill = SolidColor(Color.Blue)
+                )
+                // A thick stroke
+                Path(
+                    pathData = PathData {
+                        moveTo(0.0f, 0.0f)
+                        lineTo(sizePx, 0.0f)
+                    },
+                    stroke = SolidColor(Color.Yellow),
+                    strokeLineWidth = strokeWidthPx,
+                )
+            },
+            alignment = alignment
+        )
+        AtLeastSize(size = minimumSize, modifier = background) {
+        }
+    }
+
+    @Composable
     private fun VectorMirror(size: Int): VectorPainter {
         val sizePx = size.toFloat()
         val sizeDp = (size / LocalDensity.current.density).dp
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
index 9c256c7..2eed50f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
@@ -214,6 +214,7 @@
     var strokeLineWidth = DefaultStrokeLineWidth
         set(value) {
             field = value
+            isStrokeDirty = true
             invalidate()
         }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
index a6940e0..648babc7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
@@ -219,6 +219,7 @@
         return next
     }
 
+    // TODO replace with mutableStateOf(Unit, neverEqualPolicy()) after b/291647821 is addressed
     private var invalidateCount by mutableIntStateOf(0)
 
     @Composable
diff --git a/constraintlayout/constraintlayout-compose/api/current.txt b/constraintlayout/constraintlayout-compose/api/current.txt
index 6e529b9..3eafaf2 100644
--- a/constraintlayout/constraintlayout-compose/api/current.txt
+++ b/constraintlayout/constraintlayout-compose/api/current.txt
@@ -480,29 +480,6 @@
     property public final androidx.constraintlayout.compose.VerticalAnchorable start;
   }
 
-  public final class InvalidationStrategy {
-    ctor public InvalidationStrategy(optional androidx.constraintlayout.compose.InvalidationStrategy.OnIncomingConstraints? onIncomingConstraints, kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange);
-    method public androidx.constraintlayout.compose.InvalidationStrategy.OnIncomingConstraints? getOnIncomingConstraints();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnObservedStateChange();
-    property public final androidx.constraintlayout.compose.InvalidationStrategy.OnIncomingConstraints? onIncomingConstraints;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange;
-    field public static final androidx.constraintlayout.compose.InvalidationStrategy.Companion Companion;
-  }
-
-  public static final class InvalidationStrategy.Companion {
-    method public androidx.constraintlayout.compose.InvalidationStrategy getDefaultInvalidationStrategy();
-    property public final androidx.constraintlayout.compose.InvalidationStrategy DefaultInvalidationStrategy;
-  }
-
-  public static fun interface InvalidationStrategy.OnIncomingConstraints {
-    method public operator boolean invoke(androidx.constraintlayout.compose.InvalidationStrategyScope, long old, long new);
-  }
-
-  public final class InvalidationStrategyScope {
-    method public boolean fixedHeightRate(long oldConstraints, long newConstraints, int skipCount, int threshold);
-    method public boolean fixedWidthRate(long oldConstraints, long newConstraints, int skipCount, int threshold);
-  }
-
   public final class KeyAttributeScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
     method public float getAlpha();
     method public float getRotationX();
@@ -662,9 +639,9 @@
   }
 
   public final class MotionLayoutKt {
-    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, float progress, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.Transition? transition, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional String transitionName, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, float progress, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.Transition? transition, optional int debugFlags, optional int optimizationLevel, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional String transitionName, optional int debugFlags, optional int optimizationLevel, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class MotionLayoutScope {
diff --git a/constraintlayout/constraintlayout-compose/api/restricted_current.txt b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
index af5cd2f..60b18d4 100644
--- a/constraintlayout/constraintlayout-compose/api/restricted_current.txt
+++ b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
@@ -525,29 +525,6 @@
     property public final androidx.constraintlayout.compose.VerticalAnchorable start;
   }
 
-  public final class InvalidationStrategy {
-    ctor public InvalidationStrategy(optional androidx.constraintlayout.compose.InvalidationStrategy.OnIncomingConstraints? onIncomingConstraints, kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange);
-    method public androidx.constraintlayout.compose.InvalidationStrategy.OnIncomingConstraints? getOnIncomingConstraints();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnObservedStateChange();
-    property public final androidx.constraintlayout.compose.InvalidationStrategy.OnIncomingConstraints? onIncomingConstraints;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onObservedStateChange;
-    field public static final androidx.constraintlayout.compose.InvalidationStrategy.Companion Companion;
-  }
-
-  public static final class InvalidationStrategy.Companion {
-    method public androidx.constraintlayout.compose.InvalidationStrategy getDefaultInvalidationStrategy();
-    property public final androidx.constraintlayout.compose.InvalidationStrategy DefaultInvalidationStrategy;
-  }
-
-  public static fun interface InvalidationStrategy.OnIncomingConstraints {
-    method public operator boolean invoke(androidx.constraintlayout.compose.InvalidationStrategyScope, long old, long new);
-  }
-
-  public final class InvalidationStrategyScope {
-    method public boolean fixedHeightRate(long oldConstraints, long newConstraints, int skipCount, int threshold);
-    method public boolean fixedWidthRate(long oldConstraints, long newConstraints, int skipCount, int threshold);
-  }
-
   public final class KeyAttributeScope extends androidx.constraintlayout.compose.BaseKeyFrameScope {
     method public float getAlpha();
     method public float getRotationX();
@@ -745,12 +722,12 @@
   }
 
   public final class MotionLayoutKt {
-    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, float progress, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.Transition? transition, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional String transitionName, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, optional androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, androidx.constraintlayout.compose.Transition? transition, float progress, androidx.constraintlayout.compose.LayoutInformationReceiver? informationReceiver, int optimizationLevel, boolean showBounds, boolean showPaths, boolean showKeyPositions, androidx.compose.ui.Modifier modifier, androidx.compose.runtime.MutableState<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, float progress, String transitionName, int optimizationLevel, int debugFlags, androidx.compose.ui.Modifier modifier, androidx.compose.runtime.MutableState<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, androidx.compose.runtime.MutableState<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, androidx.constraintlayout.compose.InvalidationStrategy invalidationStrategy, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, float progress, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.Transition? transition, optional int debugFlags, optional int optimizationLevel, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional String transitionName, optional int debugFlags, optional int optimizationLevel, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, androidx.constraintlayout.compose.Transition? transition, float progress, androidx.constraintlayout.compose.LayoutInformationReceiver? informationReceiver, int optimizationLevel, boolean showBounds, boolean showPaths, boolean showKeyPositions, androidx.compose.ui.Modifier modifier, androidx.compose.runtime.State<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, float progress, String transitionName, int optimizationLevel, int debugFlags, androidx.compose.ui.Modifier modifier, androidx.compose.runtime.State<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, String? constraintSetName, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, optional int debugFlags, optional int optimizationLevel, androidx.compose.runtime.State<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class MotionLayoutScope {
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/MotionLayoutActivity.kt b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/MotionLayoutActivity.kt
index d3d54ab..91a28e8 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/MotionLayoutActivity.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/MotionLayoutActivity.kt
@@ -32,7 +32,6 @@
 import androidx.constraintlayout.compose.integration.macrobenchmark.target.graphs.DynamicGraphsPreview
 import androidx.constraintlayout.compose.integration.macrobenchmark.target.newmessage.NewMotionMessagePreview
 import androidx.constraintlayout.compose.integration.macrobenchmark.target.newmessage.NewMotionMessagePreviewWithDsl
-import androidx.constraintlayout.compose.integration.macrobenchmark.target.newmessage.NewMotionMessagePreviewWithDslOptimized
 import androidx.constraintlayout.compose.integration.macrobenchmark.target.toolbar.MotionCollapseToolbarPreview
 
 class MotionLayoutActivity : ComponentActivity() {
@@ -56,23 +55,15 @@
                         "NewMessageJson" -> {
                             NewMotionMessagePreview()
                         }
-
                         "NewMessageDsl" -> {
                             NewMotionMessagePreviewWithDsl()
                         }
-
-                        "OptimizedNewMessageDsl" -> {
-                            NewMotionMessagePreviewWithDslOptimized()
-                        }
-
                         "CollapsibleToolbar" -> {
                             MotionCollapseToolbarPreview()
                         }
-
                         "DynamicGraphs" -> {
                             DynamicGraphsPreview()
                         }
-
                         else -> {
                             throw IllegalArgumentException("No Composable with name: $name")
                         }
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt
index 7b952db..0c12a36 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt
@@ -64,7 +64,6 @@
 import androidx.constraintlayout.compose.ConstraintSet
 import androidx.constraintlayout.compose.Dimension
 import androidx.constraintlayout.compose.ExperimentalMotionApi
-import androidx.constraintlayout.compose.InvalidationStrategy
 import androidx.constraintlayout.compose.MotionLayout
 import androidx.constraintlayout.compose.MotionLayoutScope
 import androidx.constraintlayout.compose.MotionScene
@@ -76,26 +75,19 @@
 @Preview
 @Composable
 fun NewMotionMessagePreview() {
-    NewMotionMessageWithControls(useDsl = false, optimize = false)
+    NewMotionMessageWithControls(useDsl = false)
 }
 
 @Preview
 @Composable
 fun NewMotionMessagePreviewWithDsl() {
-    NewMotionMessageWithControls(useDsl = true, optimize = false)
-}
-
-@Preview
-@Composable
-fun NewMotionMessagePreviewWithDslOptimized() {
-    NewMotionMessageWithControls(useDsl = true, optimize = true)
+    NewMotionMessageWithControls(useDsl = true)
 }
 
 @OptIn(ExperimentalComposeUiApi::class, ExperimentalMotionApi::class)
 @Composable
 fun NewMotionMessageWithControls(
-    useDsl: Boolean,
-    optimize: Boolean
+    useDsl: Boolean
 ) {
     val initialLayout = NewMessageLayout.Full
     val newMessageState = rememberNewMessageState(initialLayoutState = initialLayout)
@@ -119,20 +111,10 @@
                 text = "Mini"
             )
         }
-        val invalidationStrategy = remember(newMessageState, optimize) {
-            if (optimize) {
-                InvalidationStrategy {
-                    newMessageState.currentState
-                }
-            } else {
-                InvalidationStrategy.DefaultInvalidationStrategy
-            }
-        }
         NewMessageButton(
             modifier = Modifier.fillMaxSize(),
             motionScene = motionScene,
             state = newMessageState,
-            invalidationStrategy = invalidationStrategy,
         )
     }
 }
@@ -551,18 +533,16 @@
 
 @Composable
 private fun NewMessageButton(
-    motionScene: MotionScene,
-    state: NewMessageState,
-    invalidationStrategy: InvalidationStrategy,
     modifier: Modifier = Modifier,
+    motionScene: MotionScene,
+    state: NewMessageState
 ) {
     val currentStateName = state.currentState.name
     MotionLayout(
         motionScene = motionScene,
         animationSpec = tween(700),
         constraintSetName = currentStateName,
-        modifier = modifier,
-        invalidationStrategy = invalidationStrategy
+        modifier = modifier
     ) {
         MotionMessageContent(state = state)
     }
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt
index 530ec36..73f439c 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/MotionLayoutBenchmark.kt
@@ -50,9 +50,6 @@
     @Test
     fun messageDsl() = benchmarkRule.testNewMessage(NewMessageMode.Dsl)
 
-    @Test
-    fun messageOptimizedDsl() = benchmarkRule.testNewMessage(NewMessageMode.OptimizedDsl)
-
     /**
      * Transitions the Layout through its three different ConstraintSets using the MotionScene JSON.
      */
@@ -161,8 +158,7 @@
 
     internal enum class NewMessageMode(val composableName: String) {
         Json("NewMessageJson"),
-        Dsl("NewMessageDsl"),
-        OptimizedDsl("OptimizedNewMessageDsl")
+        Dsl("NewMessageDsl")
     }
 
     private fun UiDevice.waitForComposeIdle(timeoutMs: Long = 3000) {
diff --git a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
index 7355336..88438d3 100644
--- a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
@@ -759,91 +759,6 @@
         assertEquals(IntOffset(rootSizePx - boxSizePx, 0), boxPosition)
     }
 
-    @Test
-    fun testInvalidationStrategy_onObservedStateChange() = with(rule.density) {
-        val rootSizePx = 200
-        val progress = mutableStateOf(0f)
-        val textContent = mutableStateOf("Foo")
-        val optimizeCorrectly = mutableStateOf(false)
-        val textId = "text"
-
-        rule.setContent {
-            WithConsistentTextStyle {
-                MotionLayout(
-                    motionScene = remember {
-                        MotionScene {
-                            val textRef = createRefFor(textId)
-
-                            defaultTransition(
-                                from = constraintSet {
-                                    constrain(textRef) {
-                                        centerTo(parent)
-                                    }
-                                },
-                                to = constraintSet {
-                                    constrain(textRef) {
-                                        centerTo(parent)
-                                    }
-                                }
-                            )
-                        }
-                    },
-                    progress = progress.value,
-                    modifier = Modifier.size(rootSizePx.toDp()),
-                    invalidationStrategy = remember(optimizeCorrectly.value) {
-                        if (optimizeCorrectly.value) {
-                            InvalidationStrategy {
-                                textContent.value
-                            }
-                        } else {
-                            InvalidationStrategy {
-                                // Do not invalidate on recomposition
-                            }
-                        }
-                    }
-                ) {
-                    Text(
-                        text = textContent.value,
-                        fontSize = 10.sp,
-                        modifier = Modifier.layoutTestId(textId)
-                    )
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        var actualTextSize = rule.onNodeWithTag(textId).getUnclippedBoundsInRoot()
-        assertEquals(18, actualTextSize.width.value.roundToInt())
-        assertEquals(14, actualTextSize.height.value.roundToInt())
-
-        textContent.value = "Foo\nBar"
-
-        // Because we are optimizing "incorrectly" the text layout remains unchanged
-        rule.waitForIdle()
-        actualTextSize = rule.onNodeWithTag(textId).getUnclippedBoundsInRoot()
-        assertEquals(18, actualTextSize.width.value.roundToInt())
-        assertEquals(14, actualTextSize.height.value.roundToInt())
-
-        textContent.value = "Foo"
-        optimizeCorrectly.value = true
-
-        // We change the text back and update the optimization strategy to be correct, text should
-        // be the same as in its initial state
-        rule.waitForIdle()
-        actualTextSize = rule.onNodeWithTag(textId).getUnclippedBoundsInRoot()
-        assertEquals(18, actualTextSize.width.value.roundToInt())
-        assertEquals(14, actualTextSize.height.value.roundToInt())
-
-        textContent.value = "Foo\nBar"
-
-        // With the appropriate optimization strategy, the layout is invalidated when the text
-        // changes
-        rule.waitForIdle()
-        actualTextSize = rule.onNodeWithTag(textId).getUnclippedBoundsInRoot()
-        assertEquals(18, actualTextSize.width.value.roundToInt())
-        assertEquals(25, actualTextSize.height.value.roundToInt())
-    }
-
     private fun Color.toHexString(): String = toArgb().toUInt().toString(16)
 }
 
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
index 18e7f36..eaa32df 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
@@ -26,7 +26,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableFloatState
-import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
@@ -36,7 +35,6 @@
 import androidx.compose.runtime.neverEqualPolicy
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.drawBehind
@@ -53,13 +51,11 @@
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.constraintlayout.core.widgets.Optimizer
-import kotlin.math.absoluteValue
 import kotlinx.coroutines.channels.Channel
 
 /**
@@ -118,12 +114,9 @@
  * @param modifier Modifier to apply to this layout node.
  * @param transition Defines the interpolation parameters between the [ConstraintSet]s to achieve
  * fine-tuned animations.
- * @param debugFlags Flags to enable visual debugging. [DebugFlags.None] by default.
  * @param optimizationLevel Optimization parameter for the underlying ConstraintLayout,
  * [Optimizer.OPTIMIZATION_STANDARD] by default.
- * @param invalidationStrategy Provides strategies to optimize invalidations in [MotionLayout].
- * Excessive invalidations will be the typical cause of bad performance in [MotionLayout]. See
- * [InvalidationStrategy] to learn how to apply common strategies.
+ * @param debugFlags Flags to enable visual debugging. [DebugFlags.None] by default.
  * @param content The content to be laid out by MotionLayout, note that each layout Composable
  * should be bound to an ID defined in the [ConstraintSet]s using
  * [Modifier.layoutId][androidx.compose.ui.layout.layoutId].
@@ -137,7 +130,6 @@
     transition: Transition? = null,
     debugFlags: DebugFlags = DebugFlags.None,
     optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
-    invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy,
     crossinline content: @Composable MotionLayoutScope.() -> Unit
 ) {
     /**
@@ -158,8 +150,7 @@
         // the same pass as the content. The only expected reader is our MeasurePolicy.
         contentTracker.value = Unit
 
-        if (invalidationStrategy.onObservedStateChange == null &&
-            compositionSource.value == CompositionSource.Unknown) {
+        if (compositionSource.value == CompositionSource.Unknown) {
             // Set the content as the original composition source if the MotionLayout was not
             // recomposed by the caller or by itself
             compositionSource.value = CompositionSource.Content
@@ -179,7 +170,6 @@
         modifier = modifier,
         contentTracker = contentTracker,
         compositionSource = compositionSource,
-        invalidationStrategy = invalidationStrategy,
         content = contentDelegate
     )
 }
@@ -229,12 +219,9 @@
  * @param modifier Modifier to apply to this layout node.
  * @param transitionName The name of the transition to apply on the layout. By default, it will
  * target the transition defined with [MotionSceneScope.defaultTransition].
- * @param debugFlags Flags to enable visual debugging. [DebugFlags.None] by default.
  * @param optimizationLevel Optimization parameter for the underlying ConstraintLayout,
  * [Optimizer.OPTIMIZATION_STANDARD] by default.
- * @param invalidationStrategy Provides strategies to optimize invalidations in [MotionLayout].
- * Excessive invalidations will be the typical cause of bad performance in [MotionLayout]. See
- * [InvalidationStrategy] to learn how to apply common strategies.
+ * @param debugFlags Flags to enable visual debugging. [DebugFlags.None] by default.
  * @param content The content to be laid out by MotionLayout, note that each layout Composable
  * should be bound to an ID defined in the [ConstraintSet]s using
  * [Modifier.layoutId][androidx.compose.ui.layout.layoutId].
@@ -247,7 +234,6 @@
     transitionName: String = "default",
     debugFlags: DebugFlags = DebugFlags.None,
     optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
-    invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy,
     crossinline content: @Composable (MotionLayoutScope.() -> Unit),
 ) {
     /**
@@ -268,8 +254,7 @@
         // the same pass as the content. The only expected reader is our MeasurePolicy.
         contentTracker.value = Unit
 
-        if (invalidationStrategy.onObservedStateChange == null &&
-            compositionSource.value == CompositionSource.Unknown) {
+        if (compositionSource.value == CompositionSource.Unknown) {
             // Set the content as the original composition source if the MotionLayout was not
             // recomposed by the caller or by itself
             compositionSource.value = CompositionSource.Content
@@ -286,7 +271,6 @@
         modifier = modifier,
         contentTracker = contentTracker,
         compositionSource = compositionSource,
-        invalidationStrategy = invalidationStrategy,
         content = contentDelegate
     )
 }
@@ -354,12 +338,9 @@
  * @param modifier Modifier to apply to this layout node.
  * @param finishedAnimationListener Called when an animation triggered by a change in
  * [constraintSetName] has ended.
- * @param debugFlags Flags to enable visual debugging. [DebugFlags.None] by default.
  * @param optimizationLevel Optimization parameter for the underlying ConstraintLayout,
  * [Optimizer.OPTIMIZATION_STANDARD] by default.
- * @param invalidationStrategy Provides strategies to optimize invalidations in [MotionLayout].
- * Excessive invalidations will be the typical cause of bad performance in [MotionLayout]. See
- * [InvalidationStrategy] to learn how to apply common strategies.
+ * @param debugFlags Flags to enable visual debugging. [DebugFlags.None] by default.
  * @param content The content to be laid out by MotionLayout, note that each layout Composable
  * should be bound to an ID defined in the [ConstraintSet]s using
  * [Modifier.layoutId][androidx.compose.ui.layout.layoutId].
@@ -373,7 +354,6 @@
     noinline finishedAnimationListener: (() -> Unit)? = null,
     debugFlags: DebugFlags = DebugFlags.None,
     optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
-    invalidationStrategy: InvalidationStrategy = InvalidationStrategy.DefaultInvalidationStrategy,
     @Suppress("HiddenTypeParameter")
     crossinline content: @Composable (MotionLayoutScope.() -> Unit)
 ) {
@@ -395,8 +375,7 @@
         // the same pass as the content. The only expected reader is our MeasurePolicy.
         contentTracker.value = Unit
 
-        if (invalidationStrategy.onObservedStateChange == null &&
-            compositionSource.value == CompositionSource.Unknown) {
+        if (compositionSource.value == CompositionSource.Unknown) {
             // Set the content as the original composition source if the MotionLayout was not
             // recomposed by the caller or by itself
             compositionSource.value = CompositionSource.Content
@@ -414,7 +393,6 @@
         optimizationLevel = optimizationLevel,
         contentTracker = contentTracker,
         compositionSource = compositionSource,
-        invalidationStrategy = invalidationStrategy,
         content = contentDelegate
     )
 }
@@ -429,9 +407,8 @@
     finishedAnimationListener: (() -> Unit)? = null,
     debugFlags: DebugFlags = DebugFlags.None,
     optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
-    contentTracker: MutableState<Unit>,
+    contentTracker: State<Unit>,
     compositionSource: Ref<CompositionSource>,
-    invalidationStrategy: InvalidationStrategy,
     @Suppress("HiddenTypeParameter")
     content: @Composable (MotionLayoutScope.() -> Unit)
 ) {
@@ -509,7 +486,6 @@
         modifier = modifier,
         contentTracker = contentTracker,
         compositionSource = compositionSource,
-        invalidationStrategy = invalidationStrategy,
         content = content
     )
 }
@@ -524,9 +500,8 @@
     optimizationLevel: Int,
     debugFlags: DebugFlags,
     modifier: Modifier,
-    contentTracker: MutableState<Unit>,
+    contentTracker: State<Unit>,
     compositionSource: Ref<CompositionSource>,
-    invalidationStrategy: InvalidationStrategy,
     @Suppress("HiddenTypeParameter")
     content: @Composable MotionLayoutScope.() -> Unit,
 ) {
@@ -559,7 +534,6 @@
         modifier = modifier,
         contentTracker = contentTracker,
         compositionSource = compositionSource,
-        invalidationStrategy = invalidationStrategy,
         content = content
     )
 }
@@ -577,9 +551,8 @@
     showPaths: Boolean,
     showKeyPositions: Boolean,
     modifier: Modifier,
-    contentTracker: MutableState<Unit>,
+    contentTracker: State<Unit>,
     compositionSource: Ref<CompositionSource>,
-    invalidationStrategy: InvalidationStrategy,
     @Suppress("HiddenTypeParameter")
     content: @Composable MotionLayoutScope.() -> Unit
 ) {
@@ -611,23 +584,6 @@
         true // Remember is required to return a non-Unit value
     }
 
-    if (invalidationStrategy.onObservedStateChange != null) {
-        Snapshot.observe(
-            readObserver = {
-                // Perform a reassignment to the State tracker, this will force readers to recompose at
-                // the same pass as the content. The only expected reader is our MeasurePolicy.
-                contentTracker.value = Unit
-
-                if (compositionSource.value == CompositionSource.Unknown) {
-                    // Set the content as the original composition source if the MotionLayout was not
-                    // recomposed by the caller or by itself
-                    compositionSource.value = CompositionSource.Content
-                }
-            },
-            block = invalidationStrategy.onObservedStateChange
-        )
-    }
-
     val measurePolicy = motionLayoutMeasurePolicy(
         contentTracker = contentTracker,
         compositionSource = compositionSource,
@@ -636,8 +592,7 @@
         transition = transitionImpl,
         motionProgress = motionProgress,
         measurer = measurer,
-        optimizationLevel = optimizationLevel,
-        invalidationStrategy = invalidationStrategy
+        optimizationLevel = optimizationLevel
     )
 
     measurer.addLayoutInformationReceiver(informationReceiver)
@@ -988,7 +943,6 @@
     motionProgress: MutableFloatState,
     measurer: MotionMeasurer,
     optimizationLevel: Int,
-    invalidationStrategy: InvalidationStrategy
 ): MeasurePolicy =
     MeasurePolicy { measurables, constraints ->
         // Do a state read, to guarantee that we control measure when the content recomposes without
@@ -996,16 +950,15 @@
         contentTracker.value
 
         val layoutSize = measurer.performInterpolationMeasure(
-            constraints = constraints,
-            layoutDirection = this.layoutDirection,
-            constraintSetStart = constraintSetStart,
-            constraintSetEnd = constraintSetEnd,
-            transition = transition,
-            measurables = measurables,
-            optimizationLevel = optimizationLevel,
-            progress = motionProgress.floatValue,
-            compositionSource = compositionSource.value ?: CompositionSource.Unknown,
-            invalidateOnConstraintsCallback = invalidationStrategy.shouldInvalidate
+            constraints,
+            this.layoutDirection,
+            constraintSetStart,
+            constraintSetEnd,
+            transition,
+            measurables,
+            optimizationLevel,
+            motionProgress.floatValue,
+            compositionSource.value ?: CompositionSource.Unknown
         )
         compositionSource.value = CompositionSource.Unknown // Reset after measuring
 
@@ -1196,488 +1149,3 @@
         return view.isShowingLayoutBounds
     }
 }
-
-/**
- * Helper scope that provides some strategies to improve performance based on incoming constraints.
- *
- * As a starting approach, we recommend trying the following:
- *
- * ```
- * MotionLayout(
- *     ...,
- *     invalidationStrategy = remember {
- *         InvalidationStrategy(
- *             onIncomingConstraints = { old, new ->
- *                 // We invalidate every third frame, or when the change is higher than 5 pixels
- *                 fixedWidthRate(old, new, skipCount = 3, threshold = 5) ||
- *                     fixedHeightRate(old, new, skipCount = 3, threshold = 5)
- *             },
- *             onObservedStateChange = null // Default behavior
- *         )
- * }
- * ) {
- *    // content
- * }
- * ```
- *
- * See either [fixedWidthRate] or [fixedHeightRate] to learn more about the intent behind
- * rate-limiting invalidation.
- */
-class InvalidationStrategyScope internal constructor() {
-    private var widthRateCount = 0
-
-    /**
-     * Limits the rate at which MotionLayout is invalidated while [Constraints.hasFixedWidth] is
-     * true.
-     *
-     * &nbsp;
-     *
-     * The rate limit is defined by two variables. Use [skipCount] to indicate how many consecutive
-     * measure passes should skip invalidation, you may then provide a [threshold] (in pixels) to
-     * indicate when to invalidate regardless of how many passes are left to skip. This is
-     * important since you only want to skip invalidation passes when there's **not** a significant
-     * change in dimensions.
-     *
-     * &nbsp;
-     *
-     * Overall, you don't want [skipCount] to be too high otherwise it'll result in a "jumpy" layout
-     * behavior, but you also don't want the [threshold] to be too low, otherwise you'll lose the
-     * benefit of rate limiting.
-     *
-     * A good starting point is setting [skipCount] to 3 and [threshold] to 5. You can then
-     * adjust based on your expectations of performance and perceived smoothness.
-     */
-    fun fixedWidthRate(
-        oldConstraints: Constraints,
-        newConstraints: Constraints,
-        skipCount: Int,
-        threshold: Int
-    ): Boolean {
-        if (oldConstraints.hasFixedWidth && newConstraints.hasFixedWidth) {
-            val diff = (newConstraints.maxWidth - oldConstraints.maxWidth).absoluteValue
-            if (diff >= threshold) {
-                widthRateCount = 0
-                return true
-            }
-            if (diff != 0) {
-                widthRateCount++
-                if (widthRateCount > skipCount) {
-                    widthRateCount = 0
-                    return true
-                }
-            }
-        } else {
-            widthRateCount = 0
-        }
-        return false
-    }
-
-    private var heightRateCount = 0
-
-    /**
-     * Limits the rate at which MotionLayout is invalidated while [Constraints.hasFixedHeight] is
-     * true.
-     *
-     * &nbsp;
-     *
-     * The rate limit is defined by two variables. Use [skipCount] to indicate how many consecutive
-     * measure passes should skip invalidation, you may then provide a [threshold] (in pixels) to
-     * indicate when to invalidate regardless of how many passes are left to skip. This is
-     * important since you only want to skip invalidation passes when there's **not** a significant
-     * change in dimensions.
-     *
-     * &nbsp;
-     *
-     * Overall, you don't want [skipCount] to be too high otherwise it'll result in a "jumpy" layout
-     * behavior, but you also don't want the [threshold] to be too low, otherwise you'll lose the
-     * benefit of rate limiting.
-     *
-     * A good starting point is setting [skipCount] to 3 and [threshold] to 5. You can then
-     * adjust based on your expectations of performance and perceived smoothness.
-     */
-    fun fixedHeightRate(
-        oldConstraints: Constraints,
-        newConstraints: Constraints,
-        skipCount: Int,
-        threshold: Int
-    ): Boolean {
-        if (oldConstraints.hasFixedHeight && newConstraints.hasFixedHeight) {
-            val diff = (newConstraints.maxHeight - oldConstraints.maxHeight).absoluteValue
-            if (diff >= threshold) {
-                heightRateCount = 0
-                return true
-            }
-            if (diff != 0) {
-                heightRateCount++
-                if (heightRateCount > skipCount) {
-                    heightRateCount = 0
-                    return true
-                }
-            }
-        } else {
-            heightRateCount = 0
-        }
-        return false
-    }
-}
-
-/**
- * Provide different invalidation strategies for [MotionLayout].
- *
- * &nbsp;
- *
- * Whenever [MotionLayout] needs invalidating, it has to recalculate all animations based on the
- * current state at the measure pass, this is the slowest process in the [MotionLayout] cycle.
- *
- * An invalidation can be triggered by two reasons:
- * - Incoming fixed size constraints have changed. This is necessary since layouts are highly
- * dependent on their available space, it'll typically happen if you are externally animating the
- * dimensions of [MotionLayout].
- * - The content of MotionLayout recomposes. This is necessary since Layouts in Compose don't know
- * the reason for a new measure pass, so we need to recalculate animations even if recomposition
- * didn't affect the actual Layout. For example, this **definitely** happens if you are using
- * [MotionLayoutScope.customProperties], even when you are just animating a background color, the
- * custom property will trigger a recomposition in the content and [MotionLayout] will be forced to
- * invalidate since it cannot know that the Layout was not affected.
- *
- * So, you may use [InvalidationStrategy] to help [MotionLayout] decide when to invalidate:
- *
- * - [onObservedStateChange]: Mitigates invalidation from content recomposition by explicitly
- * reading the State variables you want to cause invalidation. You'll likely want to
- * apply this strategy to most of your [MotionLayout] Composables. As, in the most simple cases you
- * can just provide an empty lambda. Here's a full example:
- *
- * ```
- * val progress = remember { Animatable(0f) }
- *
- * MotionLayout(
- *     motionScene = remember {
- *         // A simple MotionScene that animates a background color from Red to Blue
- *         MotionScene {
- *             val (textRef) = createRefsFor("text")
- *
- *             val start = constraintSet {
- *                 constrain(textRef) {
- *                     centerTo(parent)
- *                     customColor("background", Color.Red)
- *                 }
- *             }
- *             val end = constraintSet(extendConstraintSet = start) {
- *                 constrain(textRef) {
- *                     customColor("background", Color.Blue)
- *                 }
- *             }
- *             defaultTransition(from = start, to = end)
- *         }
- *     },
- *     progress = progress.value,
- *     modifier = Modifier.fillMaxSize(),
- *     invalidationStrategy = remember {
- *         InvalidationStrategy(
- *             onObservedStateChange = { /* Empty, no need to invalidate on content recomposition */  }
- *         )
- *     }
- * ) {
- *     // The content doesn't depend on any State variable that may affect the Layout's measure result
- *     Text(
- *         text = "Hello, World",
- *         modifier = Modifier
- *             .layoutId("text")
- *             // However, the custom color is causing recomposition on each animated frame
- *             .background(customColor("text", "background"))
- *     )
- * }
- * LaunchedEffect(Unit) {
- *     delay(1000)
- *     progress.animateTo(targetValue = 1f, tween(durationMillis = 1200))
- * }
- * ```
- *
- * *When should I provide States to read then?*
- *
- * &nbsp;
- *
- * Whenever a State backed variable that affects the Layout's measure result changes. The most
- * common cases are Strings on the Text Composable.
- *
- * Here's an example where the text changes half-way through the animation:
- * ```
- * val progress = remember { Animatable(0f) }
- *
- * var textString by remember { mutableStateOf("Hello, World") }
- * MotionLayout(
- *     motionScene = remember {
- *         // A MotionScene that animates a Text from one corner to the other with an animated
- *         // background color
- *         MotionScene {
- *             val (textRef) = createRefsFor("text")
- *
- *             defaultTransition(
- *                 from = constraintSet {
- *                     constrain(textRef) {
- *                         top.linkTo(parent.top)
- *                         start.linkTo(parent.start)
- *
- *                         customColor("background", Color.LightGray)
- *                     }
- *                 },
- *                 to = constraintSet {
- *                     constrain(textRef) {
- *                         bottom.linkTo(parent.bottom)
- *                         end.linkTo(parent.end)
- *
- *                         customColor("background", Color.Gray)
- *                     }
- *                 }
- *             )
- *         }
- *     },
- *     progress = progress.value,
- *     modifier = Modifier.fillMaxSize(),
- *     invalidationStrategy = remember {
- *         InvalidationStrategy(
- *             onObservedStateChange = @Suppress("UNUSED_EXPRESSION"){
- *                 // We read our State String variable in this block, to guarantee that
- *                 // MotionLayout will invalidate to accommodate the new Text Layout.
- *                 // Note that we do not read the custom color here since it doesn't affect the Layout
- *                 textString
- *             }
- *         )
- *     }
- * ) {
- *     // The text Layout will change based on the provided State String
- *     Text(
- *         text = textString,
- *         modifier = Modifier
- *             .layoutId("text")
- *             // Without an invalidation strategy, the custom color would normally invalidate
- *             // MotionLayout due to recomposition
- *             .background(customColor("text", "background"))
- *     )
- * }
- * LaunchedEffect(Unit) {
- *     delay(1000)
- *     progress.animateTo(targetValue = 1f, tween(durationMillis = 3000)) {
- *         if (value >= 0.5f) {
- *             textString = "This is a\n" + "significantly different text."
- *         }
- *     }
- * }
- * ```
- *
- * *What if my Text changes continuously?*
- *
- * &nbsp;
- *
- * There's a few strategies you can take depending on how you expect the Text to behave.
- *
- * For example, if you don't expect the text to need more than one line, you can set the Text with
- * `softWrap = false` and `overflow = TextOverflow.Visible`:
- *
- * ```
- * MotionLayout(
- *     motionScene = motionScene,
- *     progress = progress,
- *     modifier = Modifier.size(200.dp),
- *     invalidationStrategy = remember { InvalidationStrategy { /* Do not invalidate on content recomposition */  } }
- * ) {
- *     Text(
- *         text = <your-State-String>,
- *         modifier = Modifier.layoutId("text"),
- *         softWrap = false,
- *         overflow = TextOverflow.Visible
- *     )
- * }
- * ```
- *
- * The Text layout won't change significantly and performance will be much improved.
- *
- * - [onIncomingConstraints]: With this lambda you can mitigate invalidation from incoming
- * constraints. You'll only have to worry about providing this lambda if you or the Layout you're
- * using is animating measuring constraints on [MotionLayout]. If the size is only changing in specific,
- * discrete values, then you should allow [MotionLayout] to invalidate normally.
- *
- * Here's an example where we manually animate [MotionLayout]'s size through a Modifier (along with
- * the MotionLayout animation), and shows how to mitigate invalidation by rate-limiting:
- *
- * ```
- * val textId = "text"
- * val progress = remember { Animatable(0f) }
- *
- * val initial = remember { DpSize(100.dp, 100.dp) }
- * val target = remember { DpSize(120.dp, 200.dp) }
- * var size by remember { mutableStateOf(initial) }
- *
- * MotionLayout(
- *     motionScene = remember {
- *         MotionScene {
- *             val (textRef) = createRefsFor( "text")
- *
- *             // Animate text from the bottom of the layout to the top
- *             defaultTransition(
- *                 from = constraintSet {
- *                     constrain(textRef) {
- *                         centerHorizontallyTo(parent)
- *                         bottom.linkTo(parent.bottom)
- *                     }
- *                 },
- *                 to = constraintSet {
- *                     constrain(textRef) {
- *                         centerHorizontallyTo(parent)
- *                         top.linkTo(parent.top)
- *                     }
- *                 }
- *             )
- *         }
- *     },
- *     progress = progress.value,
- *     modifier = Modifier.background(Color.Cyan).size(size),
- *     invalidationStrategy = remember {
- *         InvalidationStrategy(
- *             onIncomingConstraints = { old, new ->
- *                 // We invalidate every third frame, or when the change is higher than 5 pixels
- *                 fixedWidthRate(old, new, skipCount = 3, threshold = 5) ||
- *                     fixedHeightRate(old, new, skipCount = 3, threshold = 5)
- *             },
- *             // No need to worry about content state changes for this example
- *             onObservedStateChange = {}
- *         )
- *     }
- * ) {
- *     Text("Hello, World!", Modifier.layoutId(textId))
- * }
- *
- * // Animate the size along with the MotionLayout. Without an invalidation strategy, this will cause
- * // MotionLayout to invalidate at every measure pass since it's getting fixed size Constraints at
- * // different values
- * LaunchedEffect(Unit) {
- *     val sizeDifference = target - initial
- *     delay(1000)
- *     progress.animateTo(1f, tween(1200)) {
- *         size = initial + (sizeDifference * value)
- *     }
- * }
- * ```
- *
- * Note that [fixedWidthRate][InvalidationStrategyScope.fixedWidthRate] and [fixedHeightRate][InvalidationStrategyScope.fixedHeightRate]
- * are helper methods available in [InvalidationStrategyScope].
- *
- * &nbsp;
- *
- * An alternative to rate-limiting is to "simply" avoid invalidation from changed fixed size constraints.
- * This can be done by leaving [MotionLayout] as wrap content and then have it choose its own start
- * and ending size. Naturally, this is not always feasible, specially if it's a parent Composable the one
- * that's animating the size constraints.
- *
- * But, here's the MotionScene showing how to achieve this behavior based on the example above:
- *
- * ```
- * MotionScene {
- *     // We'll use fakeParentRef to choose our starting and ending size then constrain everything
- *     // else to it. MotionLayout will animate without invalidating.
- *     // There's no need to bind "fakeParent" to any actual Composable.
- *     val (fakeParentRef, textRef) = createRefsFor("fakeParent", "text")
- *
- *     defaultTransition(
- *         from = constraintSet {
- *             constrain(fakeParentRef) {
- *                 width = 100.dp.asDimension()
- *                 height = 100.dp.asDimension()
- *             }
- *
- *             constrain(textRef) {
- *                 bottom.linkTo(fakeParentRef.bottom)
- *             }
- *         },
- *         to = constraintSet {
- *             constrain(fakeParentRef) {
- *                 width = 120.dp.asDimension()
- *                 height = 200.dp.asDimension()
- *             }
- *
- *             constrain(textRef) {
- *                 top.linkTo(fakeParentRef.top)
- *             }
- *         }
- *     )
- * }
- * ```
- *
- * You can then remove the size modifier and the invalidation strategy for `onIncomingConstraints`,
- * as [MotionLayout] will animate through both sizes without invalidating.
- *
- * @see InvalidationStrategy.DefaultInvalidationStrategy
- * @see InvalidationStrategy.OnIncomingConstraints
- * @see InvalidationStrategyScope
- * @see InvalidationStrategyScope.fixedWidthRate
- * @see InvalidationStrategyScope.fixedHeightRate
- *
- * @property onObservedStateChange
- */
-class InvalidationStrategy(
-    val onIncomingConstraints: OnIncomingConstraints? = null,
-    /**
-     * Lambda to implement invalidation on observed State changes.
-     *
-     * [State][androidx.compose.runtime.State] based variables should be read in the block of
-     * this lambda to have [MotionLayout] invalidate whenever any of those variables
-     * have changed.
-     *
-     * You may use an assigned value or delegated variable for this purpose:
-     * ```
-     * val stateVar0 = remember { mutableStateOf("Foo") }
-     * var stateVar1 by remember { mutableStateOf("Bar") }
-     * val invalidationStrategy = remember {
-     *     InvalidationStrategy(
-     *         onObservedStateChange = @Suppress("UNUSED_EXPRESSION") {
-     *             stateVar0.value
-     *             stateVar1
-     *         }
-     *     )
-     * }
-     * ```
-     *
-     * See [InvalidationStrategy] to learn more about common strategies regarding invalidation on
-     * onObservedStateChange.
-     */
-    val onObservedStateChange: (() -> Unit)?
-) {
-    private val scope = InvalidationStrategyScope()
-
-    /**
-     * Hacky thing to transform: `(InvalidationStrategyScope.(old: Constraints, new: Constraints) -> Boolean)?`
-     * into `((old: Constraints, new: Constraints) -> Boolean)?`
-     */
-    internal val shouldInvalidate: ShouldInvalidateCallback? = kotlin.run {
-        if (onIncomingConstraints == null) {
-            null
-        } else {
-            ShouldInvalidateCallback { old, new ->
-                with(onIncomingConstraints) {
-                    scope(old, new)
-                }
-            }
-        }
-    }
-
-    companion object {
-        /**
-         * Default invalidation strategy for [MotionLayout].
-         *
-         * This will cause it to invalidate whenever its content recomposes or when it receives different
-         * fixed size [Constraints] at the measure pass.
-         */
-        val DefaultInvalidationStrategy = InvalidationStrategy(null, null)
-    }
-
-    /**
-     * Functional interface to implement invalidation on incoming constraints.
-     *
-     * See [InvalidationStrategy] or either of [fixedWidthRate][InvalidationStrategyScope.fixedWidthRate]/[fixedHeightRate][InvalidationStrategyScope.fixedHeightRate].
-     *
-     * To learn some strategies on how to improve invalidation due to incoming constraints.
-     */
-    fun interface OnIncomingConstraints {
-        operator fun InvalidationStrategyScope.invoke(old: Constraints, new: Constraints): Boolean
-    }
-}
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
index e3b4da5..a8a1f1c 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
@@ -85,14 +85,10 @@
         measurables: List<Measurable>,
         optimizationLevel: Int,
         progress: Float,
-        compositionSource: CompositionSource,
-        invalidateOnConstraintsCallback: ShouldInvalidateCallback?
+        compositionSource: CompositionSource
     ): IntSize {
-        val needsRemeasure = needsRemeasure(
-            constraints = constraints,
-            source = compositionSource,
-            invalidateOnConstraintsCallback = invalidateOnConstraintsCallback
-        )
+        val needsRemeasure = needsRemeasure(constraints, compositionSource)
+
         if (lastProgressInInterpolation != progress ||
             (layoutInformationReceiver?.getForcedWidth() != Int.MIN_VALUE &&
                 layoutInformationReceiver?.getForcedHeight() != Int.MIN_VALUE) ||
@@ -110,19 +106,10 @@
                 remeasure = needsRemeasure
             )
         }
-        oldConstraints = constraints
         return IntSize(root.width, root.height)
     }
 
     /**
-     * Nullable reference of [Constraints] used for the `invalidateOnConstraintsCallback`.
-     *
-     * Helps us to indicate when we can start calling the callback, as we need at least one measure
-     * pass to populate this reference.
-     */
-    private var oldConstraints: Constraints? = null
-
-    /**
      * Indicates if the layout requires measuring before computing the interpolation.
      *
      * This might happen if the size of MotionLayout or any of its children changed.
@@ -130,28 +117,17 @@
      * MotionLayout size might change from its parent Layout, and in some cases the children size
      * might change (eg: A Text layout has a longer string appended).
      */
-    private fun needsRemeasure(
-        constraints: Constraints,
-        source: CompositionSource,
-        invalidateOnConstraintsCallback: ShouldInvalidateCallback?
-    ): Boolean {
+    private fun needsRemeasure(constraints: Constraints, source: CompositionSource): Boolean {
         if (this.transition.isEmpty || frameCache.isEmpty()) {
             // Nothing measured (by MotionMeasurer)
             return true
         }
 
-        if (oldConstraints != null && invalidateOnConstraintsCallback != null) {
-            if (invalidateOnConstraintsCallback(oldConstraints!!, constraints)) {
-                // User is deciding when to invalidate
-                return true
-            }
-        } else {
-            if ((constraints.hasFixedHeight && !state.sameFixedHeight(constraints.maxHeight)) ||
-                (constraints.hasFixedWidth && !state.sameFixedWidth(constraints.maxWidth))
-            ) {
-                // Layout size changed
-                return true
-            }
+        if ((constraints.hasFixedHeight && !state.sameFixedHeight(constraints.maxHeight)) ||
+            (constraints.hasFixedWidth && !state.sameFixedWidth(constraints.maxWidth))
+        ) {
+            // Layout size changed
+            return true
         }
 
         // Content recomposed
@@ -566,11 +542,3 @@
         transition.applyAllTo(this.transition)
     }
 }
-
-/**
- * Functional interface to represent the callback of type
- * `(old: Constraints, new: Constraints) -> Boolean`
- */
-internal fun interface ShouldInvalidateCallback {
-    operator fun invoke(old: Constraints, new: Constraints): Boolean
-}
diff --git a/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceRetrieverTest.kt b/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceRetrieverTest.kt
new file mode 100644
index 0000000..b45d2d7
--- /dev/null
+++ b/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceRetrieverTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 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.core.performance.testing
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+/** Unit test for [FakeDevicePerformanceRetriever]. */
+class FakeDevicePerformanceRetrieverTest {
+
+    @Test
+    fun mediaPerformanceClass_30() {
+        val retriever = FakeDevicePerformanceRetriever(30)
+        val mpc = retriever.getPerformanceClass()
+        assertThat(mpc).isEqualTo(30)
+    }
+}
diff --git a/core/core-performance/src/test/java/androidx/core/performance/MediaPerformanceTest.kt b/core/core-performance/src/test/java/androidx/core/performance/MediaPerformanceTest.kt
new file mode 100644
index 0000000..2f30c43
--- /dev/null
+++ b/core/core-performance/src/test/java/androidx/core/performance/MediaPerformanceTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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.core.performance
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+/** Unit tests for [MediaPerformance]. */
+class MediaPerformanceTest {
+
+    @Test
+    fun mediaPerformanceClassWithUnspecifiedRetriever() {
+        val mpc = MediaPerformance.getPerformanceClass()
+        assertThat(mpc).isEqualTo(0)
+    }
+
+    @Test
+    fun mediaPerformanceClassWithSpecifiedDefaultRetriever() {
+        val mpc = MediaPerformance.getPerformanceClass(
+            DefaultDevicePerformanceRetriever()
+        )
+        assertThat(mpc).isEqualTo(0)
+    }
+}
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 408df66..efce220 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -1324,4 +1324,7 @@
 # > Task :compose:ui:ui:compileReleaseKotlinAndroid
 e: Daemon compilation failed: Could not connect to Kotlin compile daemon
 java\.lang\.RuntimeException: Could not connect to Kotlin compile daemon
-Errors were stored into \$SUPPORT/\.gradle/kotlin/errors/errors\-[0-9]+\.log
\ No newline at end of file
+Errors were stored into \$SUPPORT/\.gradle/kotlin/errors/errors\-[0-9]+\.log
+# :benchmark:benchmark-baseline-profile-gradle-plugin:jar https://github.com/gradle/gradle/issues/22545
+# TODO(aurimas): remove when upgrading to Gradle 8.3
+.*No valid plugin descriptors were found in META\-INF/gradle-plugins.*
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 842f536..853eb1f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
 # TODO(https://github.com/spdx/spdx-gradle-plugin/issues/16) remove `-DSPDXParser.OnlyUseLocalLicenses=true`
-org.gradle.jvmargs=-Xmx8g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true -Dorg.gradle.configuration-cache.internal.load-after-store=false -DSPDXParser.OnlyUseLocalLicenses=true
+org.gradle.jvmargs=-Xmx8g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true -DSPDXParser.OnlyUseLocalLicenses=true
 org.gradle.daemon=true
 org.gradle.configureondemand=true
 org.gradle.parallel=true
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index c25c109..2418bf7 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -16,6 +16,7 @@
 
 import androidx.build.LibraryType
 import androidx.build.SdkHelperKt
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -31,18 +32,29 @@
     kapt(libs.autoService)
     compileOnly(libs.gradleIncapHelper)
     kapt(libs.gradleIncapHelperProcessor)
-    implementation(libs.autoCommon)
+    implementation(project(":room:room-compiler-processing"))
     implementation(libs.javapoet)
+    implementation(libs.kspApi)
 
     testImplementation(project(":hilt:hilt-common"))
+    testImplementation(project(":annotation:annotation"))
     testImplementation(libs.junit)
-    testImplementation(libs.truth)
-    testImplementation(libs.googleCompileTesting)
+    testImplementation(project(":kruth:kruth"))
+    testImplementation(project(":room:room-compiler-processing-testing"))
     testImplementation(libs.hiltCore)
     testImplementationAarAsJar(project(":hilt:hilt-work"))
     testImplementation(SdkHelperKt.getSdkDependency(project))
 }
 
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions {
+        freeCompilerArgs += [
+                "-opt-in=androidx.room.compiler.processing.ExperimentalProcessingApi",
+                "-opt-in=com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview"
+        ]
+    }
+}
+
 tasks.withType(Test).configureEach {
     // https://github.com/google/compile-testing/issues/222
     it.jvmArgs "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltKspProcessor.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltKspProcessor.kt
new file mode 100644
index 0000000..7c6fef4
--- /dev/null
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltKspProcessor.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+import androidx.hilt.work.WorkerStep
+import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
+import com.google.auto.service.AutoService
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+
+class AndroidXHiltKspProcessor(
+    environment: SymbolProcessorEnvironment
+) : KspBasicAnnotationProcessor(environment) {
+    override fun processingSteps() = listOf(WorkerStep())
+
+    @AutoService(SymbolProcessorProvider::class)
+    class Provider : SymbolProcessorProvider {
+        override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+            return AndroidXHiltKspProcessor(environment)
+        }
+    }
+}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt
index 277140b..cf646af 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/AndroidXHiltProcessor.kt
@@ -17,13 +17,10 @@
 package androidx.hilt
 
 import androidx.hilt.work.WorkerStep
+import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor
 import com.google.auto.service.AutoService
-import javax.annotation.processing.AbstractProcessor
 import javax.annotation.processing.Processor
-import javax.annotation.processing.RoundEnvironment
 import javax.lang.model.SourceVersion
-import javax.lang.model.element.Element
-import javax.lang.model.element.TypeElement
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING
 
@@ -32,32 +29,9 @@
  */
 @AutoService(Processor::class)
 @IncrementalAnnotationProcessor(ISOLATING)
-class AndroidXHiltProcessor : AbstractProcessor() {
+class AndroidXHiltProcessor : JavacBasicAnnotationProcessor() {
 
-    override fun getSupportedAnnotationTypes() = setOf(
-        ClassNames.HILT_WORKER.canonicalName()
-    )
+    override fun processingSteps() = listOf(WorkerStep())
 
     override fun getSupportedSourceVersion() = SourceVersion.latest()
-
-    override fun process(
-        annotations: MutableSet<out TypeElement>,
-        roundEnv: RoundEnvironment
-    ): Boolean {
-        getSteps().forEach { step ->
-            annotations.firstOrNull { it.qualifiedName.contentEquals(step.annotation()) }?.let {
-                step.process(roundEnv.getElementsAnnotatedWith(it))
-            }
-        }
-        return false
-    }
-
-    private fun getSteps() = listOf(
-        WorkerStep(processingEnv)
-    )
-
-    interface Step {
-        fun annotation(): String
-        fun process(annotatedElements: Set<Element>)
-    }
 }
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/AssistedFactoryGenerator.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/AssistedFactoryGenerator.kt
deleted file mode 100644
index b452df0..0000000
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/AssistedFactoryGenerator.kt
+++ /dev/null
@@ -1,116 +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.hilt.assisted
-
-import androidx.hilt.ClassNames
-import androidx.hilt.ext.L
-import androidx.hilt.ext.T
-import androidx.hilt.ext.W
-import androidx.hilt.ext.addGeneratedAnnotation
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.CodeBlock
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.JavaFile
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.TypeElement
-import javax.lang.model.util.ElementFilter
-
-/**
- * Source generator for assisted factories.
- */
-internal class AssistedFactoryGenerator(
-    private val processingEnv: ProcessingEnvironment,
-    private val productClassName: ClassName,
-    private val factoryClassName: ClassName,
-    private val factorySuperTypeName: ParameterizedTypeName,
-    private val originatingElement: TypeElement,
-    private val dependencyRequests: List<DependencyRequest>
-) {
-
-    fun generate() {
-        val factoryTypeSpec = TypeSpec.classBuilder(factoryClassName)
-            .addOriginatingElement(originatingElement)
-            .addSuperinterface(factorySuperTypeName)
-            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
-            .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
-            .addFields(getFieldSpecs())
-            .addMethod(getConstructorMethodSpec())
-            .addMethod(getCreateMethodSpec())
-            .build()
-        JavaFile.builder(factoryClassName.packageName(), factoryTypeSpec)
-            .build()
-            .writeTo(processingEnv.filer)
-    }
-
-    private fun getFieldSpecs() = dependencyRequests
-        .filterNot { it.isAssisted }
-        .map { dependencyRequest ->
-            val fieldTypeName = dependencyRequest.providerTypeName.withoutAnnotations()
-            FieldSpec.builder(fieldTypeName, dependencyRequest.name)
-                .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
-                .build()
-        }
-
-    private fun getConstructorMethodSpec() =
-        MethodSpec.constructorBuilder()
-            .addAnnotation(ClassNames.INJECT)
-            .apply {
-                dependencyRequests
-                    .filterNot { it.isAssisted }
-                    .forEach { dependencyRequest ->
-                        addParameter(dependencyRequest.providerTypeName, dependencyRequest.name)
-                        addStatement("this.$1N = $1N", dependencyRequest.name)
-                    }
-            }
-            .build()
-
-    private fun getCreateMethodSpec(): MethodSpec {
-        val factoryTypeElement =
-            processingEnv.elementUtils.getTypeElement(factorySuperTypeName.rawType.canonicalName())
-        val factoryMethod = ElementFilter.methodsIn(factoryTypeElement.enclosedElements).first()
-        val parameterSpecs = factoryMethod.parameters.map { ParameterSpec.get(it) }
-        val constructorArgs = dependencyRequests.map {
-            val paramLiteral = when {
-                it.isAssisted -> {
-                    factoryMethod.parameters.first { param ->
-                        TypeName.get(param.asType()) == it.type
-                    }.simpleName.toString()
-                }
-                it.isProvider -> it.name
-                else -> "${it.name}.get()"
-            }
-            CodeBlock.of(L, paramLiteral)
-        }
-        return MethodSpec.methodBuilder(factoryMethod.simpleName.toString())
-            .addAnnotation(Override::class.java)
-            .addAnnotation(ClassNames.NON_NULL)
-            .addModifiers(Modifier.PUBLIC)
-            .returns(productClassName)
-            .addParameters(parameterSpecs)
-            .addStatement(
-                "return new $T($L)",
-                productClassName, CodeBlock.join(constructorArgs, ",$W")
-            )
-            .build()
-    }
-}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt
index 822c778..6130cc7c 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/assisted/DependencyRequest.kt
@@ -17,11 +17,12 @@
 package androidx.hilt.assisted
 
 import androidx.hilt.ClassNames
-import androidx.hilt.ext.hasAnnotation
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.processing.XVariableElement
+import androidx.room.compiler.processing.toAnnotationSpec
 import com.squareup.javapoet.AnnotationSpec
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
-import javax.lang.model.element.VariableElement
 
 /**
  * Data class that represents a binding request for an assisted injected type.
@@ -51,17 +52,16 @@
     }
 }
 
-internal fun VariableElement.toDependencyRequest(): DependencyRequest {
-    val qualifier = annotationMirrors.find {
-        it.annotationType.asElement().hasAnnotation("javax.inject.Qualifier")
-    }?.let { AnnotationSpec.get(it) }
-    val type = TypeName.get(asType())
+internal fun XVariableElement.toDependencyRequest(): DependencyRequest {
+    val qualifier = getAllAnnotations().find {
+        it.qualifiedName == "javax.inject.Qualifier"
+    }?.toAnnotationSpec(includeDefaultValues = false)
     return DependencyRequest(
-        name = simpleName.toString(),
-        type = type,
+        name = this.name,
+        type = this.type.asTypeName().toJavaPoet(),
         isAssisted = (
-            hasAnnotation(ClassNames.ANDROIDX_ASSISTED.canonicalName()) ||
-                hasAnnotation(ClassNames.ASSISTED.canonicalName())
+            this.hasAnnotation(ClassNames.ANDROIDX_ASSISTED) ||
+                this.hasAnnotation(ClassNames.ASSISTED)
             ) && qualifier == null,
         qualifier = qualifier
     )
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/annotationProcessing.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/annotationProcessing.kt
deleted file mode 100644
index 21827d2..0000000
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/annotationProcessing.kt
+++ /dev/null
@@ -1,28 +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.hilt.ext
-
-import com.google.auto.common.MoreElements
-import javax.lang.model.element.Element
-import kotlin.reflect.KClass
-
-fun Element.hasAnnotation(clazz: KClass<out Annotation>) =
-    MoreElements.isAnnotationPresent(this, clazz.java)
-
-fun Element.hasAnnotation(qName: String) = annotationMirrors.any {
-    MoreElements.asType(it.annotationType.asElement()).qualifiedName.contentEquals(qName)
-}
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt
index d3fc1c2..9aae5b2 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/ext/javaPoet.kt
@@ -17,10 +17,10 @@
 package androidx.hilt.ext
 
 import androidx.hilt.AndroidXHiltProcessor
-import com.google.auto.common.GeneratedAnnotationSpecs
+import androidx.room.compiler.processing.XProcessingEnv
+import com.squareup.javapoet.AnnotationSpec
+import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.TypeSpec
-import javax.lang.model.SourceVersion
-import javax.lang.model.util.Elements
 
 const val L = "\$L"
 const val T = "\$T"
@@ -28,15 +28,13 @@
 const val S = "\$S"
 const val W = "\$W"
 
-internal fun TypeSpec.Builder.addGeneratedAnnotation(
-    elements: Elements,
-    sourceVersion: SourceVersion
-) = apply {
-    GeneratedAnnotationSpecs.generatedAnnotationSpec(
-        elements,
-        sourceVersion,
-        AndroidXHiltProcessor::class.java
-    ).ifPresent { generatedAnnotation ->
-        addAnnotation(generatedAnnotation)
+internal fun TypeSpec.Builder.addGeneratedAnnotation(env: XProcessingEnv) = apply {
+    env.findGeneratedAnnotation()?.let {
+        addAnnotation(
+            AnnotationSpec.builder(ClassName.bestGuess(it.asClassName().canonicalName))
+                .addMember("value", S, AndroidXHiltProcessor::class.java.canonicalName)
+                .build()
+
+        )
     }
 }
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElements.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElement.kt
similarity index 75%
rename from hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElements.kt
rename to hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElement.kt
index f62c1ee..52d4ff4 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElements.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerElement.kt
@@ -18,23 +18,23 @@
 
 import androidx.hilt.ClassNames
 import androidx.hilt.assisted.toDependencyRequest
-import com.google.auto.common.MoreElements
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.processing.XConstructorElement
+import androidx.room.compiler.processing.XTypeElement
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.ParameterizedTypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
 
 /**
  * Data class that represents a Hilt injected Worker
  */
-internal data class WorkerElements(
-    val typeElement: TypeElement,
-    val constructorElement: ExecutableElement
+internal data class WorkerElement(
+    val typeElement: XTypeElement,
+    val constructorElement: XConstructorElement
 ) {
-    val className = ClassName.get(typeElement)
+    val className = typeElement.asClassName().toJavaPoet()
 
     val factoryClassName = ClassName.get(
-        MoreElements.getPackage(typeElement).qualifiedName.toString(),
+        typeElement.packageName,
         "${className.simpleNames().joinToString("_")}_AssistedFactory"
     )
 
@@ -44,7 +44,7 @@
     )
 
     val moduleClassName = ClassName.get(
-        MoreElements.getPackage(typeElement).qualifiedName.toString(),
+        typeElement.packageName,
         "${className.simpleNames().joinToString("_")}_HiltModule"
     )
 
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt
index 4052322..155f2e4 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerGenerator.kt
@@ -20,13 +20,15 @@
 import androidx.hilt.ext.S
 import androidx.hilt.ext.T
 import androidx.hilt.ext.addGeneratedAnnotation
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.addOriginatingElement
+import androidx.room.compiler.processing.writeTo
 import com.squareup.javapoet.AnnotationSpec
 import com.squareup.javapoet.JavaFile
 import com.squareup.javapoet.MethodSpec
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeSpec
 import com.squareup.javapoet.WildcardTypeName
-import javax.annotation.processing.ProcessingEnvironment
 import javax.lang.model.element.Modifier
 
 /**
@@ -52,13 +54,13 @@
  * ```
  */
 internal class WorkerGenerator(
-    private val processingEnv: ProcessingEnvironment,
-    private val injectedWorker: WorkerElements
+    private val processingEnv: XProcessingEnv,
+    private val injectedWorker: WorkerElement
 ) {
     fun generate() {
         val assistedFactoryTypeSpec = TypeSpec.interfaceBuilder(injectedWorker.factoryClassName)
             .addOriginatingElement(injectedWorker.typeElement)
-            .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
+            .addGeneratedAnnotation(processingEnv)
             .addAnnotation(ClassNames.ASSISTED_FACTORY)
             .addModifiers(Modifier.PUBLIC)
             .addSuperinterface(injectedWorker.factorySuperTypeName)
@@ -69,7 +71,7 @@
 
         val hiltModuleTypeSpec = TypeSpec.interfaceBuilder(injectedWorker.moduleClassName)
             .addOriginatingElement(injectedWorker.typeElement)
-            .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
+            .addGeneratedAnnotation(processingEnv)
             .addAnnotation(ClassNames.MODULE)
             .addAnnotation(
                 AnnotationSpec.builder(ClassNames.INSTALL_IN)
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
index d06abf9..a02521a 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
@@ -16,99 +16,83 @@
 
 package androidx.hilt.work
 
-import androidx.hilt.AndroidXHiltProcessor
 import androidx.hilt.ClassNames
-import androidx.hilt.ext.hasAnnotation
-import com.google.auto.common.MoreElements
-import com.squareup.javapoet.TypeName
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Element
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.NestingKind
-import javax.lang.model.element.TypeElement
-import javax.lang.model.util.ElementFilter
+import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XProcessingStep
+import androidx.room.compiler.processing.XTypeElement
 import javax.tools.Diagnostic
 
 /**
  * Processing step that generates code enabling assisted injection of Workers using Hilt.
  */
-class WorkerStep(
-    private val processingEnv: ProcessingEnvironment
-) : AndroidXHiltProcessor.Step {
+class WorkerStep : XProcessingStep {
 
-    private val elements = processingEnv.elementUtils
-    private val types = processingEnv.typeUtils
-    private val messager = processingEnv.messager
+    override fun annotations() = setOf(ClassNames.HILT_WORKER.canonicalName())
 
-    override fun annotation() = ClassNames.HILT_WORKER.canonicalName()
-
-    override fun process(annotatedElements: Set<Element>) {
-        val parsedElements = mutableSetOf<TypeElement>()
-        annotatedElements.forEach { element ->
-            val typeElement = MoreElements.asType(element)
-            if (parsedElements.add(typeElement)) {
-                parse(typeElement)?.let { worker ->
-                    WorkerGenerator(
-                        processingEnv,
-                        worker
-                    ).generate()
-                }
-            }
-        }
+    override fun process(
+        env: XProcessingEnv,
+        elementsByAnnotation: Map<String, Set<XElement>>,
+        isLastRound: Boolean
+    ): Set<XElement> {
+        elementsByAnnotation[ClassNames.HILT_WORKER.canonicalName()]
+            ?.filterIsInstance<XTypeElement>()
+            ?.mapNotNull { element -> parse(env, element) }
+            ?.forEach { worker -> WorkerGenerator(env, worker).generate() }
+        return emptySet()
     }
 
-    private fun parse(typeElement: TypeElement): WorkerElements? {
+    private fun parse(env: XProcessingEnv, workerTypeElement: XTypeElement): WorkerElement? {
         var valid = true
 
-        if (elements.getTypeElement(ClassNames.WORKER_ASSISTED_FACTORY.toString()) == null) {
-            error(
+        if (env.findTypeElement(ClassNames.WORKER_ASSISTED_FACTORY) == null) {
+            env.error(
                 "To use @HiltWorker you must add the 'work' artifact. " +
                     "androidx.hilt:hilt-work:<version>"
             )
             valid = false
         }
 
-        if (!types.isSubtype(
-                typeElement.asType(),
-                elements.getTypeElement(ClassNames.LISTENABLE_WORKER.toString()).asType()
-            )
-        ) {
-            error(
+        val workerType = workerTypeElement.type
+        val listenableWorkerType = env.requireType(ClassNames.LISTENABLE_WORKER)
+        if (!listenableWorkerType.isAssignableFrom(workerType)) {
+            env.error(
                 "@HiltWorker is only supported on types that subclass " +
-                    "${ClassNames.LISTENABLE_WORKER}."
+                    "${ClassNames.LISTENABLE_WORKER}.",
+                workerTypeElement
             )
             valid = false
         }
 
-        val constructors = ElementFilter.constructorsIn(typeElement.enclosedElements).filter {
-            if (it.hasAnnotation(ClassNames.INJECT.canonicalName())) {
-                error(
+        val constructors = workerTypeElement.getConstructors().filter {
+            if (it.hasAnnotation(ClassNames.INJECT)) {
+                env.error(
                     "Worker constructor should be annotated with @AssistedInject instead of " +
-                        "@Inject."
+                        "@Inject.",
+                    it
                 )
                 valid = false
             }
-            it.hasAnnotation(ClassNames.ASSISTED_INJECT.canonicalName())
+            it.hasAnnotation(ClassNames.ASSISTED_INJECT)
         }
         if (constructors.size != 1) {
-            error(
+            env.error(
                 "@HiltWorker annotated class should contain exactly one @AssistedInject " +
                     "annotated constructor.",
-                typeElement
+                workerTypeElement
             )
             valid = false
         }
-        constructors.filter { it.modifiers.contains(Modifier.PRIVATE) }.forEach {
-            error("@AssistedInject annotated constructors must not be private.", it)
+        constructors.filter { it.isPrivate() }.forEach {
+            env.error("@AssistedInject annotated constructors must not be private.", it)
             valid = false
         }
 
-        if (typeElement.nestingKind == NestingKind.MEMBER &&
-            !typeElement.modifiers.contains(Modifier.STATIC)
-        ) {
-            error(
+        if (workerTypeElement.isNested() && !workerTypeElement.isStatic()) {
+            env.error(
                 "@HiltWorker may only be used on inner classes if they are static.",
-                typeElement
+                workerTypeElement
             )
             valid = false
         }
@@ -119,35 +103,40 @@
         var contextIndex = -1
         var workerParametersIndex = -1
         injectConstructor.parameters.forEachIndexed { index, param ->
-            if (TypeName.get(param.asType()) == ClassNames.CONTEXT) {
-                if (!param.hasAnnotation(ClassNames.ASSISTED.canonicalName())) {
-                    error("Missing @Assisted annotation in param '${param.simpleName}'.", param)
+            if (param.type.asTypeName().toJavaPoet() == ClassNames.CONTEXT) {
+                if (!param.hasAnnotation(ClassNames.ASSISTED)) {
+                    env.error("Missing @Assisted annotation in param '${param.name}'.", param)
                     valid = false
                 }
                 contextIndex = index
             }
-            if (TypeName.get(param.asType()) == ClassNames.WORKER_PARAMETERS) {
-                if (!param.hasAnnotation(ClassNames.ASSISTED.canonicalName())) {
-                    error("Missing @Assisted annotation in param '${param.simpleName}'.", param)
+            if (param.type.asTypeName().toJavaPoet() == ClassNames.WORKER_PARAMETERS) {
+                if (!param.hasAnnotation(ClassNames.ASSISTED)) {
+                    env.error("Missing @Assisted annotation in param '${param.name}'.", param)
                     valid = false
                 }
                 workerParametersIndex = index
             }
         }
         if (contextIndex > workerParametersIndex) {
-            error(
+            env.error(
                 "The 'Context' parameter must be declared before the 'WorkerParameters' in the " +
                     "@AssistedInject constructor of a @HiltWorker annotated class.",
                 injectConstructor
             )
+            valid = false
         }
 
         if (!valid) return null
 
-        return WorkerElements(typeElement, injectConstructor)
+        return WorkerElement(workerTypeElement, injectConstructor)
     }
 
-    private fun error(message: String, element: Element? = null) {
-        messager.printMessage(Diagnostic.Kind.ERROR, message, element)
+    private fun XProcessingEnv.error(message: String, element: XElement? = null) {
+        if (element != null) {
+            messager.printMessage(Diagnostic.Kind.ERROR, message, element)
+        } else {
+            messager.printMessage(Diagnostic.Kind.ERROR, message)
+        }
     }
 }
diff --git a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/testUtils.kt b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/ext/testUtils.kt
similarity index 68%
rename from hilt/hilt-compiler/src/test/kotlin/androidx/hilt/testUtils.kt
rename to hilt/hilt-compiler/src/test/kotlin/androidx/hilt/ext/testUtils.kt
index b24ad5e..722c714 100644
--- a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/testUtils.kt
+++ b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/ext/testUtils.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 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.
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-package androidx.hilt
+package androidx.hilt.ext
 
-import com.google.testing.compile.Compiler
-import com.google.testing.compile.Compiler.javac
-import com.google.testing.compile.JavaFileObjects
+import androidx.hilt.ClassNames
+import androidx.room.compiler.processing.util.Source
 import java.io.File
-import javax.tools.JavaFileObject
 
 val GENERATED_TYPE = try {
     Class.forName("javax.annotation.processing.Generated")
@@ -55,11 +53,7 @@
     }
 }
 
-fun loadJavaSource(fileName: String, qName: String): JavaFileObject {
-    val contents = File("src/test/data/sources/$fileName").readText(Charsets.UTF_8)
-    return JavaFileObjects.forSourceString(qName, contents)
-}
-
-fun compiler(): Compiler = javac().withProcessors(AndroidXHiltProcessor())
-
-fun String.toJFO(qName: String) = JavaFileObjects.forSourceString(qName, this.trimIndent())
+fun loadJavaSource(fileName: String, qName: String) = Source.loadJavaSource(
+    file = File("src/test/data/sources/$fileName"),
+    qName = qName
+)
diff --git a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt
index fc892a0..b9d87f4 100644
--- a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt
+++ b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerGeneratorTest.kt
@@ -16,12 +16,11 @@
 
 package androidx.hilt.work
 
-import androidx.hilt.GENERATED_ANNOTATION
-import androidx.hilt.GENERATED_TYPE
-import androidx.hilt.Sources
-import androidx.hilt.compiler
-import androidx.hilt.toJFO
-import com.google.testing.compile.CompilationSubject.assertThat
+import androidx.hilt.ext.GENERATED_ANNOTATION
+import androidx.hilt.ext.GENERATED_TYPE
+import androidx.hilt.ext.Sources
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -31,111 +30,128 @@
 
     @Test
     fun verifyAssistedFactory_mixedArgs() {
-        val foo = """
-        package androidx.hilt.work.test;
+        val foo = Source.java(
+            "androidx.hilt.work.test.Foo",
+            """
+            package androidx.hilt.work.test;
 
-        public class Foo { }
-        """.toJFO("androidx.hilt.work.test.Foo")
+            public class Foo { }
+            """
+        )
 
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.MyWorker",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.Worker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
-        import java.lang.String;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.Worker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
+            import java.lang.String;
 
-        @HiltWorker
-        class MyWorker extends Worker {
-            @AssistedInject
-            MyWorker(@Assisted Context context, @Assisted WorkerParameters params, String s,
-                    Foo f, long l) {
-                super(context, params);
+            @HiltWorker
+            class MyWorker extends Worker {
+                @AssistedInject
+                MyWorker(@Assisted Context context, @Assisted WorkerParameters params, String s,
+                        Foo f, long l) {
+                    super(context, params);
+                }
             }
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker")
+            """
+        )
 
-        val expected = """
-        package androidx.hilt.work.test;
+        val expected = Source.java(
+            "androidx.hilt.work.test.MyWorker_AssistedFactory",
+            """
+            package androidx.hilt.work.test;
 
-        import androidx.hilt.work.WorkerAssistedFactory;
-        import dagger.assisted.AssistedFactory;
-        import $GENERATED_TYPE;
+            import androidx.hilt.work.WorkerAssistedFactory;
+            import dagger.assisted.AssistedFactory;
+            import $GENERATED_TYPE;
 
-        $GENERATED_ANNOTATION
-        @AssistedFactory
-        public interface MyWorker_AssistedFactory extends WorkerAssistedFactory<MyWorker> {
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker_AssistedFactory")
+            $GENERATED_ANNOTATION
+            @AssistedFactory
+            public interface MyWorker_AssistedFactory extends WorkerAssistedFactory<MyWorker> {
+            }
+            """
+        )
 
-        val compilation = compiler()
-            .compile(
+        runProcessorTest(
+            sources = listOf(
                 foo, myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER,
                 Sources.WORKER_PARAMETERS
-            )
-        assertThat(compilation).apply {
-            succeeded()
-            generatedSourceFile("androidx.hilt.work.test.MyWorker_AssistedFactory")
-                .hasSourceEquivalentTo(expected)
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.generatedSource(expected)
         }
     }
 
     @Test
     fun verifyMultibindModule() {
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.MyWorker",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.Worker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.Worker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
 
-        @HiltWorker
-        class MyWorker extends Worker {
-            @AssistedInject
-            MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
-                super(context, params);
+            @HiltWorker
+            class MyWorker extends Worker {
+                @AssistedInject
+                MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+                    super(context, params);
+                }
             }
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker")
+            """
+        )
 
-        val expected = """
-        package androidx.hilt.work.test;
+        val expected = Source.java(
+            "androidx.hilt.work.test.MyWorker_HiltModule",
+            """
+            package androidx.hilt.work.test;
 
-        import androidx.hilt.work.WorkerAssistedFactory;
-        import androidx.work.ListenableWorker;
-        import dagger.Binds;
-        import dagger.Module;
-        import dagger.hilt.InstallIn;
-        import dagger.hilt.codegen.OriginatingElement;
-        import dagger.hilt.components.SingletonComponent;
-        import dagger.multibindings.IntoMap;
-        import dagger.multibindings.StringKey;
-        import $GENERATED_TYPE;
+            import androidx.hilt.work.WorkerAssistedFactory;
+            import androidx.work.ListenableWorker;
+            import dagger.Binds;
+            import dagger.Module;
+            import dagger.hilt.InstallIn;
+            import dagger.hilt.codegen.OriginatingElement;
+            import dagger.hilt.components.SingletonComponent;
+            import dagger.multibindings.IntoMap;
+            import dagger.multibindings.StringKey;
+            import $GENERATED_TYPE;
 
-        $GENERATED_ANNOTATION
-        @Module
-        @InstallIn(SingletonComponent.class)
-        @OriginatingElement(topLevelClass = MyWorker.class)
-        public interface MyWorker_HiltModule {
-            @Binds
-            @IntoMap
-            @StringKey("androidx.hilt.work.test.MyWorker")
-            WorkerAssistedFactory<? extends ListenableWorker> bind(MyWorker_AssistedFactory factory)
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker_HiltModule")
+            $GENERATED_ANNOTATION
+            @Module
+            @InstallIn(SingletonComponent.class)
+            @OriginatingElement(
+                topLevelClass = MyWorker.class
+            )
+            public interface MyWorker_HiltModule {
+                @Binds
+                @IntoMap
+                @StringKey("androidx.hilt.work.test.MyWorker")
+                WorkerAssistedFactory<? extends ListenableWorker> bind(MyWorker_AssistedFactory factory);
+            }
+            """
+        )
 
-        val compilation = compiler()
-            .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
-        assertThat(compilation).apply {
-            succeeded()
-            generatedSourceFile("androidx.hilt.work.test.MyWorker_HiltModule")
-                .hasSourceEquivalentTo(expected)
+        runProcessorTest(
+            sources = listOf(
+                myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.generatedSource(expected)
         }
     }
 }
diff --git a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt
index 5d7c63a..2c6022e 100644
--- a/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt
+++ b/hilt/hilt-compiler/src/test/kotlin/androidx/hilt/work/WorkerStepTest.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalProcessingApi::class)
+
 package androidx.hilt.work
 
-import androidx.hilt.Sources
-import androidx.hilt.compiler
-import androidx.hilt.toJFO
-import com.google.testing.compile.CompilationSubject.assertThat
+import androidx.hilt.ext.Sources
+import androidx.room.compiler.processing.ExperimentalProcessingApi
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -29,28 +31,33 @@
 
     @Test
     fun verifyEnclosingElementExtendsWorker() {
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.MyWorker",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
 
-        @HiltWorker
-        class MyWorker {
-            @AssistedInject
-            MyWorker(@Assisted Context context, @Assisted WorkerParameters params) { }
-        }
-        """.toJFO("androidx.hilt.work.work.MyWorker")
+            @HiltWorker
+            class MyWorker {
+                @AssistedInject
+                MyWorker(@Assisted Context context, @Assisted WorkerParameters params) { }
+            }
+            """
+        )
 
-        val compilation = compiler()
-            .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
-        assertThat(compilation).apply {
-            failed()
-            hadErrorCount(1)
-            hadErrorContainingMatch(
+        runProcessorTest(
+            sources = listOf(
+                myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.hasErrorCount(1)
+            it.hasErrorContaining(
                 "@HiltWorker is only supported on types that subclass " +
                     "androidx.work.ListenableWorker."
             )
@@ -59,37 +66,42 @@
 
     @Test
     fun verifySingleAnnotatedConstructor() {
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.MyWorker",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.Worker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
-        import java.lang.String;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.Worker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
+            import java.lang.String;
 
-        @HiltWorker
-        class MyWorker extends Worker {
-            @AssistedInject
-            MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
-                super(context, params);
+            @HiltWorker
+            class MyWorker extends Worker {
+                @AssistedInject
+                MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+                    super(context, params);
+                }
+
+                @AssistedInject
+                MyWorker(Context context, WorkerParameters params, String s) {
+                    super(context, params);
+                }
             }
+            """
+        )
 
-            @AssistedInject
-            MyWorker(Context context, WorkerParameters params, String s) {
-                super(context, params);
-            }
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker")
-
-        val compilation = compiler()
-            .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
-        assertThat(compilation).apply {
-            failed()
-            hadErrorCount(1)
-            hadErrorContainingMatch(
+        runProcessorTest(
+            sources = listOf(
+                myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.hasErrorCount(1)
+            it.hasErrorContaining(
                 "@HiltWorker annotated class should contain exactly one @AssistedInject " +
                     "annotated constructor."
             )
@@ -98,31 +110,36 @@
 
     @Test
     fun verifyNonPrivateConstructor() {
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.MyWorker",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.Worker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.Worker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
 
-        @HiltWorker
-        class MyWorker extends Worker {
-            @AssistedInject
-            private MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
-                super(context, params);
+            @HiltWorker
+            class MyWorker extends Worker {
+                @AssistedInject
+                private MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+                    super(context, params);
+                }
             }
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker")
+            """
+        )
 
-        val compilation = compiler()
-            .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
-        assertThat(compilation).apply {
-            failed()
-            hadErrorCount(1)
-            hadErrorContainingMatch(
+        runProcessorTest(
+            sources = listOf(
+                myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.hasErrorCount(1)
+            it.hasErrorContaining(
                 "@AssistedInject annotated constructors must not be private."
             )
         }
@@ -130,33 +147,38 @@
 
     @Test
     fun verifyInnerClassIsStatic() {
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.Outer",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.Worker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.Worker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
 
-        class Outer {
-            @HiltWorker
-            class MyWorker extends Worker {
-                @AssistedInject
-                MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
-                    super(context, params);
+            class Outer {
+                @HiltWorker
+                class MyWorker extends Worker {
+                    @AssistedInject
+                    MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+                        super(context, params);
+                    }
                 }
             }
-        }
-        """.toJFO("androidx.hilt.work.test.Outer")
+            """
+        )
 
-        val compilation = compiler()
-            .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
-        assertThat(compilation).apply {
-            failed()
-            hadErrorCount(1)
-            hadErrorContainingMatch(
+        runProcessorTest(
+            sources = listOf(
+                myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.hasErrorCount(1)
+            it.hasErrorContaining(
                 "@HiltWorker may only be used on inner classes " +
                     "if they are static."
             )
@@ -165,32 +187,37 @@
 
     @Test
     fun verifyConstructorAnnotation() {
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.MyWorker",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.Worker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
-        import java.lang.String;
-        import javax.inject.Inject;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.Worker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
+            import java.lang.String;
+            import javax.inject.Inject;
 
-        @HiltWorker
-        class MyWorker extends Worker {
-            @Inject
-            MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
-                super(context, params);
+            @HiltWorker
+            class MyWorker extends Worker {
+                @Inject
+                MyWorker(@Assisted Context context, @Assisted WorkerParameters params) {
+                    super(context, params);
+                }
             }
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker")
+            """
+        )
 
-        val compilation = compiler()
-            .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
-        assertThat(compilation).apply {
-            failed()
-            hadErrorContainingMatch(
+        runProcessorTest(
+            sources = listOf(
+                myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.hasErrorContaining(
                 "Worker constructor should be annotated with @AssistedInject instead of @Inject."
             )
         }
@@ -198,31 +225,36 @@
 
     @Test
     fun verifyAssistedParamOrder() {
-        val myWorker = """
-        package androidx.hilt.work.test;
+        val myWorker = Source.java(
+            "androidx.hilt.work.test.MyWorker",
+            """
+            package androidx.hilt.work.test;
 
-        import android.content.Context;
-        import androidx.hilt.work.HiltWorker;
-        import androidx.work.Worker;
-        import androidx.work.WorkerParameters;
-        import dagger.assisted.Assisted;
-        import dagger.assisted.AssistedInject;
-        import java.lang.String;
+            import android.content.Context;
+            import androidx.hilt.work.HiltWorker;
+            import androidx.work.Worker;
+            import androidx.work.WorkerParameters;
+            import dagger.assisted.Assisted;
+            import dagger.assisted.AssistedInject;
+            import java.lang.String;
 
-        @HiltWorker
-        class MyWorker extends Worker {
-            @AssistedInject
-            MyWorker(@Assisted WorkerParameters params, @Assisted Context context) {
-                super(context, params);
+            @HiltWorker
+            class MyWorker extends Worker {
+                @AssistedInject
+                MyWorker(@Assisted WorkerParameters params, @Assisted Context context) {
+                    super(context, params);
+                }
             }
-        }
-        """.toJFO("androidx.hilt.work.test.MyWorker")
+            """
+        )
 
-        val compilation = compiler()
-            .compile(myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS)
-        assertThat(compilation).apply {
-            failed()
-            hadErrorContainingMatch(
+        runProcessorTest(
+            sources = listOf(
+                myWorker, Sources.LISTENABLE_WORKER, Sources.WORKER, Sources.WORKER_PARAMETERS
+            ),
+            createProcessingSteps = { listOf(WorkerStep()) }
+        ) {
+            it.hasErrorContaining(
                 "The 'Context' parameter must be declared before the 'WorkerParameters' in the " +
                     "@AssistedInject constructor of a @HiltWorker annotated class.",
             )
diff --git a/navigation/navigation-safe-args-generator/build.gradle b/navigation/navigation-safe-args-generator/build.gradle
index c63d57d..4a019c6 100644
--- a/navigation/navigation-safe-args-generator/build.gradle
+++ b/navigation/navigation-safe-args-generator/build.gradle
@@ -24,6 +24,14 @@
 
 androidx.configureAarAsJarForConfiguration("testImplementation")
 
+// android.jar and xmlpull has the same classes, but android.jar has stubs instead of real
+// implementation, so we remove org.xmlpull.* from android.jar used for tests
+tasks.register("strippedAndroidJar", Jar).configure {
+    it.from(zipTree(SdkHelperKt.getSdkDependency(project).getFiles().first()))
+    it.exclude("org/xmlpull/**")
+    it.archiveFileName.set("stripped-android.jar")
+}
+
 dependencies {
     implementation(libs.xpp3)
     implementation(libs.xmlpull)
@@ -37,24 +45,16 @@
     testImplementation(projectOrArtifact(":room:room-compiler-processing-testing"), {
         exclude group: "androidx.room", module: "room-compiler-processing"
     })
-    testImplementation(SdkHelperKt.getSdkDependency(project))
     testImplementationAarAsJar(project(":navigation:navigation-common"))
     testImplementationAarAsJar(project(":lifecycle:lifecycle-viewmodel-savedstate"))
+    testImplementation(tasks.named("strippedAndroidJar").map { it.outputs.files })
 }
 
-tasks.findByName("test").doFirst {
-    // android.jar and xmlpull has the same classes, but android.jar has stubs instead of real
-    // implementation, so we move android.jar to end of classpath
-    def classpath = it.classpath.getFiles()
-    def androidJar = classpath.find { it.name == "android.jar" }
-    it.classpath = files(classpath.minus(androidJar).plus(androidJar))
-}
 tasks.withType(Test).configureEach {
     // https://github.com/google/compile-testing/issues/222
     it.jvmArgs "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"
 }
 
-
 androidx {
     name = "Navigation TypeSafe Arguments Generator"
     type = LibraryType.OTHER_CODE_PROCESSOR
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt
index 3d907a4..68cd698 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerRemoveShownItemsTest.kt
@@ -90,10 +90,18 @@
         mLayoutManager.waitForLayout(2)
 
         // .. then the views after the removed view are moved into the gap
-        assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
-        assertEquals(expectedResultingAdapterPosition, llm.findFirstCompletelyVisibleItemPosition())
-        assertEquals(expectedResultingAdapterPosition, llm.findLastCompletelyVisibleItemPosition())
-        assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+        mActivityRule.runOnUiThread {
+            assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
+            assertEquals(
+                expectedResultingAdapterPosition,
+                llm.findFirstCompletelyVisibleItemPosition()
+            )
+            assertEquals(
+                expectedResultingAdapterPosition,
+                llm.findLastCompletelyVisibleItemPosition()
+            )
+            assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+        }
     }
 
     /**
@@ -136,13 +144,18 @@
         mLayoutManager.waitForLayout(2)
 
         // .. then the views after the removed view are moved into the gap
-        assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
-        assertEquals(expectedResultingAdapterPosition, llm.findFirstCompletelyVisibleItemPosition())
-        assertEquals(
-            expectedResultingAdapterPosition + 1,
-            llm.findLastCompletelyVisibleItemPosition()
-        )
-        assertEquals(expectedResultingAdapterPosition + 1, llm.findLastVisibleItemPosition())
+        mActivityRule.runOnUiThread {
+            assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
+            assertEquals(
+                expectedResultingAdapterPosition,
+                llm.findFirstCompletelyVisibleItemPosition()
+            )
+            assertEquals(
+                expectedResultingAdapterPosition + 1,
+                llm.findLastCompletelyVisibleItemPosition()
+            )
+            assertEquals(expectedResultingAdapterPosition + 1, llm.findLastVisibleItemPosition())
+        }
     }
 
     /**
@@ -183,13 +196,21 @@
         mLayoutManager.waitForLayout(2)
 
         // .. then the views after the removed view are moved into the gap
-        assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
-        assertEquals(expectedResultingAdapterPosition, llm.findFirstCompletelyVisibleItemPosition())
-        assertEquals(expectedResultingAdapterPosition, llm.findLastCompletelyVisibleItemPosition())
-        assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+        mActivityRule.runOnUiThread {
+            assertEquals(expectedResultingAdapterPosition, llm.findFirstVisibleItemPosition())
+            assertEquals(
+                expectedResultingAdapterPosition,
+                llm.findFirstCompletelyVisibleItemPosition()
+            )
+            assertEquals(
+                expectedResultingAdapterPosition,
+                llm.findLastCompletelyVisibleItemPosition()
+            )
+            assertEquals(expectedResultingAdapterPosition, llm.findLastVisibleItemPosition())
+        }
     }
 
-    private inner class MyLayoutManager internal constructor(context: Context, val itemSize: Int) :
+    private inner class MyLayoutManager(context: Context, val itemSize: Int) :
         WrappedLinearLayoutManager(context, config.mOrientation, config.mReverseLayout) {
 
         override fun calculateExtraLayoutSpace(
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
index fa7b4d7..08e61c1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
@@ -161,9 +161,13 @@
      * * thrown types are copied if the backing element is from java
      */
     @JvmStatic
+    @JvmOverloads
     fun overriding(
         elm: XMethodElement,
-        owner: XType
+        owner: XType =
+            checkNotNull(elm.enclosingElement.type) {
+                "Cannot override method without enclosing class"
+            }
     ): MethodSpec.Builder {
         val asMember = elm.asMemberOf(owner)
         return overriding(
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
index f7dae3e..402ab9e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
@@ -169,12 +169,26 @@
                     NestingKind.MEMBER, NestingKind.LOCAL ->
                         enclosingElement.internalName + "$" + simpleName
                     NestingKind.ANONYMOUS ->
-                        error("Unsupported nesting $nestingKind")
+                        elementError("Unsupported nesting $nestingKind", this)
                     else ->
-                        error("Unsupported, nestingKind == null")
+                        elementError("Unsupported, nestingKind == null", this)
                 }
             is ExecutableElement -> enclosingElement.internalName
             is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
             else -> simpleName.toString()
         }
+
+    /**
+     * Throws an exception with the error [msg] and the [element] and its enclosing elements appended.
+     */
+    private fun elementError(msg: String, element: Element): Nothing {
+        fun buildName(element: Element): String {
+            val enclosingPart =
+                element.enclosingElement?.let { buildName(it) + "." } ?: ""
+            val simpleName = element.simpleName.ifEmpty { "<unnamed>" }
+            return enclosingPart + simpleName
+        }
+        val name = buildName(element)
+        error("$msg - On element $name")
+    }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index c427a9b..eee3dd6 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -43,7 +43,8 @@
     // if true, pre-compile sources then run the test to account for changes between .class files
     // and source files
     val preCompiledCode: Boolean,
-    val shouldMarkParamsFinal: Boolean
+    val shouldMarkParamsFinal: Boolean,
+    val ignoreOwner: Boolean
 ) {
     @Test
     fun javaOverrides() {
@@ -460,17 +461,15 @@
         ) { invocation ->
             val (target, methods) = invocation.getOverrideTestTargets(ignoreInheritedMethods)
             methods.forEachIndexed { index, method ->
-                val func = if (shouldMarkParamsFinal)
-                    MethodSpecHelper::overridingWithFinalParams
-                else
-                    MethodSpecHelper::overriding
+                val subject =
+                    if (ignoreOwner)
+                        MethodSpecHelper.overriding(method)
+                    else if (shouldMarkParamsFinal)
+                        MethodSpecHelper.overridingWithFinalParams(method, target.type)
+                    else
+                        MethodSpecHelper.overriding(method, target.type)
 
-                val subject = func(
-                    method,
-                    target.type
-                ).toSignature()
-
-                assertThat(subject).isEqualTo(golden[index])
+                assertThat(subject.toSignature()).isEqualTo(golden[index])
             }
         }
     }
@@ -573,6 +572,9 @@
         owner: DeclaredType,
         typeUtils: Types
     ): MethodSpec.Builder {
+        if (ignoreOwner) {
+          return MethodSpec.overriding(elm)
+        }
         val baseSpec = MethodSpec.overriding(elm, owner, typeUtils)
             .build()
 
@@ -600,7 +602,10 @@
 
     companion object {
         @JvmStatic
-        @Parameterized.Parameters(name = "preCompiledCode={0}, shouldMarkParamsFinal={1}")
-        fun params() = generateAllEnumerations(listOf(false, true), listOf(false, true))
+        @Parameterized.Parameters(
+            name = "preCompiledCode={0}, shouldMarkParamsFinal={1}, ignoreOwner={2}")
+        fun params() =
+            generateAllEnumerations(
+                listOf(false, true), listOf(false, true), listOf(false, true))
     }
 }
diff --git a/transition/transition/api/current.txt b/transition/transition/api/current.txt
index 672d6db..8b6b763 100644
--- a/transition/transition/api/current.txt
+++ b/transition/transition/api/current.txt
@@ -212,6 +212,7 @@
     method public static void endTransitions(android.view.ViewGroup?);
     method public static void go(androidx.transition.Scene);
     method public static void go(androidx.transition.Scene, androidx.transition.Transition?);
+    method public static androidx.transition.TransitionSeekController? seekTo(androidx.transition.Scene, androidx.transition.Transition);
     method public void setTransition(androidx.transition.Scene, androidx.transition.Scene, androidx.transition.Transition?);
     method public void setTransition(androidx.transition.Scene, androidx.transition.Transition?);
     method public void transitionTo(androidx.transition.Scene);
@@ -225,14 +226,18 @@
   }
 
   public interface TransitionSeekController {
+    method public void addOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
     method public void addOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
     method public void animateToEnd();
     method public void animateToStart();
-    method public long getCurrentPlayTimeMillis();
-    method public long getDurationMillis();
+    method @FloatRange(from=0.0, to=1.0) public float getCurrentFraction();
+    method @IntRange(from=0) public long getCurrentPlayTimeMillis();
+    method @IntRange(from=0) public long getDurationMillis();
     method public boolean isReady();
+    method public void removeOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
     method public void removeOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
-    method public void setCurrentPlayTimeMillis(long);
+    method public void setCurrentFraction(@FloatRange(from=0.0, to=1.0) float);
+    method public void setCurrentPlayTimeMillis(@IntRange(from=0) long);
   }
 
   public class TransitionSet extends androidx.transition.Transition {
diff --git a/transition/transition/api/restricted_current.txt b/transition/transition/api/restricted_current.txt
index 187290f..bd19de1 100644
--- a/transition/transition/api/restricted_current.txt
+++ b/transition/transition/api/restricted_current.txt
@@ -245,6 +245,7 @@
     method public static void endTransitions(android.view.ViewGroup?);
     method public static void go(androidx.transition.Scene);
     method public static void go(androidx.transition.Scene, androidx.transition.Transition?);
+    method public static androidx.transition.TransitionSeekController? seekTo(androidx.transition.Scene, androidx.transition.Transition);
     method public void setTransition(androidx.transition.Scene, androidx.transition.Scene, androidx.transition.Transition?);
     method public void setTransition(androidx.transition.Scene, androidx.transition.Transition?);
     method public void transitionTo(androidx.transition.Scene);
@@ -258,14 +259,18 @@
   }
 
   public interface TransitionSeekController {
+    method public void addOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
     method public void addOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
     method public void animateToEnd();
     method public void animateToStart();
-    method public long getCurrentPlayTimeMillis();
-    method public long getDurationMillis();
+    method @FloatRange(from=0.0, to=1.0) public float getCurrentFraction();
+    method @IntRange(from=0) public long getCurrentPlayTimeMillis();
+    method @IntRange(from=0) public long getDurationMillis();
     method public boolean isReady();
+    method public void removeOnProgressChangedListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
     method public void removeOnReadyListener(androidx.core.util.Consumer<androidx.transition.TransitionSeekController!>);
-    method public void setCurrentPlayTimeMillis(long);
+    method public void setCurrentFraction(@FloatRange(from=0.0, to=1.0) float);
+    method public void setCurrentPlayTimeMillis(@IntRange(from=0) long);
   }
 
   public class TransitionSet extends androidx.transition.Transition {
diff --git a/transition/transition/build.gradle b/transition/transition/build.gradle
index b899cd4..8ed8a8e 100644
--- a/transition/transition/build.gradle
+++ b/transition/transition/build.gradle
@@ -12,6 +12,7 @@
     implementation("androidx.collection:collection:1.1.0")
     compileOnly("androidx.fragment:fragment:1.2.5")
     compileOnly("androidx.appcompat:appcompat:1.0.1")
+    implementation("androidx.dynamicanimation:dynamicanimation:1.0.0")
 
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.testExtJunit)
diff --git a/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt b/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt
index 1700300..852db4e 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/SeekTransitionTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.AnimationDurationScaleRule.Companion.createForAllTests
+import androidx.testutils.PollingCheck
 import androidx.transition.Transition.TransitionListener
 import androidx.transition.test.R
 import com.google.common.truth.Truth.assertThat
@@ -43,7 +44,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
 @MediumTest
 class SeekTransitionTest : BaseTest() {
     @get:Rule
@@ -69,7 +70,6 @@
     @Test(expected = IllegalArgumentException::class)
     @UiThreadTest
     fun onlySeekingTransitions() {
-        if (Build.VERSION.SDK_INT < 34) throw IllegalArgumentException()
         transition = object : Visibility() {}
         TransitionManager.controlDelayedTransition(root, transition)
         fail("Expected IllegalArgumentException")
@@ -77,7 +77,6 @@
 
     @Test
     fun waitForReady() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         @Suppress("UNCHECKED_CAST")
@@ -99,7 +98,6 @@
 
     @Test
     fun waitForReadyNoChange() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         @Suppress("UNCHECKED_CAST")
@@ -120,7 +118,6 @@
 
     @Test
     fun addListenerAfterReady() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         @Suppress("UNCHECKED_CAST")
@@ -148,7 +145,6 @@
 
     @Test
     fun seekTransition() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         val listener = spy(TransitionListenerAdapter())
@@ -170,15 +166,60 @@
 
             assertThat(seekController.durationMillis).isEqualTo(300)
             assertThat(seekController.currentPlayTimeMillis).isEqualTo(0)
+            assertThat(seekController.currentFraction).isEqualTo(0f)
 
             assertThat(view.transitionAlpha).isEqualTo(1f)
 
             seekController.currentPlayTimeMillis = 150
+            assertThat(seekController.currentFraction).isEqualTo(0.5f)
             assertThat(view.transitionAlpha).isEqualTo(0.5f)
             seekController.currentPlayTimeMillis = 299
+            assertThat(seekController.currentFraction).isWithin(0.001f).of(299f / 300f)
             assertThat(view.transitionAlpha).isWithin(0.001f).of(1f / 300f)
             seekController.currentPlayTimeMillis = 300
+            assertThat(seekController.currentFraction).isEqualTo(1f)
+            verify(listener, times(1)).onTransitionEnd(any())
 
+            assertThat(view.transitionAlpha).isEqualTo(1f)
+            assertThat(view.visibility).isEqualTo(View.GONE)
+        }
+    }
+
+    @Test
+    fun seekTransitionWithFraction() {
+        lateinit var seekController: TransitionSeekController
+
+        val listener = spy(TransitionListenerAdapter())
+        transition.addListener(listener)
+
+        rule.runOnUiThread {
+            val controller = TransitionManager.controlDelayedTransition(root, transition)
+            assertThat(controller).isNotNull()
+            seekController = controller!!
+            assertThat(seekController.isReady).isFalse()
+            view.visibility = View.GONE
+        }
+
+        verify(listener, timeout(1000)).onTransitionStart(any())
+        verify(listener, times(0)).onTransitionEnd(any())
+
+        rule.runOnUiThread {
+            assertThat(view.visibility).isEqualTo(View.VISIBLE)
+
+            assertThat(seekController.durationMillis).isEqualTo(300)
+            assertThat(seekController.currentPlayTimeMillis).isEqualTo(0)
+            assertThat(seekController.currentFraction).isEqualTo(0f)
+
+            assertThat(view.transitionAlpha).isEqualTo(1f)
+
+            seekController.currentFraction = 0.5f
+            assertThat(seekController.currentPlayTimeMillis).isEqualTo(150)
+            assertThat(view.transitionAlpha).isEqualTo(0.5f)
+            seekController.currentFraction = 299f / 300f
+            assertThat(seekController.currentPlayTimeMillis).isEqualTo(299)
+            assertThat(view.transitionAlpha).isWithin(0.001f).of(1f / 300f)
+            seekController.currentFraction = 1f
+            assertThat(seekController.currentPlayTimeMillis).isEqualTo(300)
             verify(listener, times(1)).onTransitionEnd(any())
 
             assertThat(view.transitionAlpha).isEqualTo(1f)
@@ -188,7 +229,6 @@
 
     @Test
     fun animationDoesNotTakeOverSeek() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         val stateListener1 = spy(TransitionListenerAdapter())
@@ -231,7 +271,6 @@
 
     @Test
     fun seekCannotTakeOverAnimation() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         val stateListener1 = spy(TransitionListenerAdapter())
@@ -271,7 +310,6 @@
 
     @Test
     fun seekCannotTakeOverSeek() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController1: TransitionSeekController
 
         val stateListener1 = spy(TransitionListenerAdapter())
@@ -317,7 +355,6 @@
 
     @Test
     fun seekReplacesSeek() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController1: TransitionSeekController
 
         val stateListener1 = spy(TransitionListenerAdapter())
@@ -360,7 +397,6 @@
 
     @Test
     fun animateToEnd() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         val listener = spy(TransitionListenerAdapter())
@@ -387,7 +423,6 @@
 
     @Test
     fun animateToStart() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         val listener = spy(TransitionListenerAdapter())
@@ -428,7 +463,6 @@
 
     @Test
     fun animateToStartAfterAnimateToEnd() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         val listener = spy(TransitionListenerAdapter())
@@ -448,7 +482,7 @@
             seekController.animateToStart()
         }
 
-        verify(listener, timeout(3000)).onTransitionEnd(any())
+        verify(listener, timeout(3000)).onTransitionEnd(any(), eq(true))
 
         rule.runOnUiThread {
             assertThat(view.visibility).isEqualTo(View.VISIBLE)
@@ -458,7 +492,6 @@
 
     @Test
     fun animateToEndAfterAnimateToStart() {
-        if (Build.VERSION.SDK_INT < 34) return
         lateinit var seekController: TransitionSeekController
 
         val listener = spy(TransitionListenerAdapter())
@@ -488,7 +521,6 @@
 
     @Test(expected = IllegalStateException::class)
     fun seekAfterAnimate() {
-        if (Build.VERSION.SDK_INT < 34) throw IllegalStateException("Not supported before U")
         lateinit var seekController: TransitionSeekController
         transition.duration = 5000
 
@@ -507,9 +539,28 @@
         }
     }
 
+    @Test(expected = IllegalStateException::class)
+    fun seekFractionAfterAnimate() {
+        lateinit var seekController: TransitionSeekController
+        transition.duration = 5000
+
+        rule.runOnUiThread {
+            seekController = TransitionManager.controlDelayedTransition(root, transition)!!
+            view.visibility = View.GONE
+        }
+
+        rule.runOnUiThread {
+            seekController.currentFraction = 0.5f
+            seekController.animateToEnd()
+        }
+
+        rule.runOnUiThread {
+            seekController.currentFraction = 0.2f
+        }
+    }
+
     @Test
     fun seekTransitionSet() {
-        if (Build.VERSION.SDK_INT < 34) return
         transition = TransitionSet().also {
             it.addTransition(Fade(Fade.MODE_OUT))
                 .addTransition(Fade(Fade.MODE_IN))
@@ -579,7 +630,6 @@
 
     @Test
     fun animateToEndTransitionSet() {
-        if (Build.VERSION.SDK_INT < 34) return
         transition = TransitionSet().also {
             it.addTransition(Fade(Fade.MODE_OUT))
                 .addTransition(Fade(Fade.MODE_IN))
@@ -629,7 +679,6 @@
 
     @Test
     fun animateToStartTransitionSet() {
-        if (Build.VERSION.SDK_INT < 34) return
         transition = TransitionSet().also {
             it.addTransition(Fade(Fade.MODE_OUT))
                 .addTransition(Fade(Fade.MODE_IN))
@@ -695,7 +744,6 @@
 
     @Test
     fun cancelPartOfTransitionSet() {
-        if (Build.VERSION.SDK_INT < 34) return
         transition = TransitionSet().also {
             it.addTransition(Fade(Fade.MODE_OUT))
                 .addTransition(Fade(Fade.MODE_IN))
@@ -763,7 +811,6 @@
 
     @Test
     fun onTransitionCallsForwardAndReversed() {
-        if (Build.VERSION.SDK_INT < 34) return
         val listener = spy(TransitionListenerAdapter())
         transition = Fade()
         transition.addListener(listener)
@@ -792,7 +839,6 @@
 
     @Test
     fun onTransitionCallsForwardAndReversedTransitionSet() {
-        if (Build.VERSION.SDK_INT < 34) return
         val fadeOut = Fade(Fade.MODE_OUT)
         val outListener = spy(TransitionListenerAdapter())
         fadeOut.addListener(outListener)
@@ -881,7 +927,6 @@
 
     @Test
     fun pauseResumeOnSeek() {
-        if (Build.VERSION.SDK_INT < 34) return
         var pauseCount = 0
         var resumeCount = 0
         var setPauseCount = 0
@@ -942,4 +987,131 @@
             assertThat(setResumeCount).isEqualTo(1)
         }
     }
+
+    @Test
+    fun animationListener() {
+        lateinit var seekController: TransitionSeekController
+        var animatedFraction = -1f
+        var animatedMillis = -1L
+        rule.runOnUiThread {
+            seekController = TransitionManager.controlDelayedTransition(root, Fade())!!
+            view.visibility = View.GONE
+
+            seekController.addOnProgressChangedListener {
+                animatedFraction = it.currentFraction
+                animatedMillis = it.currentPlayTimeMillis
+            }
+        }
+
+        rule.runOnUiThread {
+            assertThat(animatedFraction).isEqualTo(0f)
+            assertThat(animatedMillis).isEqualTo(0)
+            seekController.currentFraction = 0.25f
+            assertThat(animatedFraction).isEqualTo(0.25f)
+            assertThat(animatedMillis).isEqualTo(75)
+            seekController.animateToEnd()
+        }
+
+        PollingCheck.waitFor {
+            animatedFraction == 1f
+        }
+    }
+
+    @Test
+    fun animationListenerRemoval() {
+        lateinit var seekController: TransitionSeekController
+        rule.runOnUiThread {
+            seekController = TransitionManager.controlDelayedTransition(root, Fade())!!
+            view.visibility = View.GONE
+        }
+
+        var animatedFraction = -1f
+        var animatedMillis = -1L
+        val removeListener = object : Consumer<TransitionSeekController> {
+            override fun accept(t: TransitionSeekController?) {
+                seekController.removeOnProgressChangedListener(this)
+            }
+        }
+        seekController.addOnProgressChangedListener(removeListener)
+        val changeListener = Consumer<TransitionSeekController> {
+            animatedFraction = it.currentFraction
+            animatedMillis = it.currentPlayTimeMillis
+        }
+        seekController.addOnProgressChangedListener(changeListener)
+
+        rule.runOnUiThread {
+            assertThat(animatedFraction).isEqualTo(0f)
+            assertThat(animatedMillis).isEqualTo(0)
+            seekController.removeOnProgressChangedListener(changeListener)
+            seekController.currentFraction = 0.25f
+            assertThat(animatedFraction).isEqualTo(0)
+            assertThat(animatedMillis).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun seekToScene() {
+        lateinit var seekController: TransitionSeekController
+        val scene1 = Scene(root, view)
+        val view2 = View(view.context)
+        val scene2 = Scene(root, view2)
+        rule.runOnUiThread {
+            TransitionManager.go(scene1)
+        }
+
+        rule.runOnUiThread {
+            val controller = TransitionManager.seekTo(scene2, Fade())
+            assertThat(controller).isNotNull()
+            seekController = controller!!
+        }
+
+        rule.runOnUiThread {
+            assertThat(seekController.currentFraction).isEqualTo(0f)
+            assertThat(view.visibility).isEqualTo(View.VISIBLE)
+            assertThat(view.transitionAlpha).isEqualTo(1f)
+            assertThat(view.isAttachedToWindow).isTrue()
+            assertThat(view2.visibility).isEqualTo(View.VISIBLE)
+            assertThat(view2.transitionAlpha).isEqualTo(0f)
+            assertThat(view2.isAttachedToWindow).isTrue()
+            seekController.currentFraction = 1f
+            assertThat(view.visibility).isEqualTo(View.VISIBLE)
+            assertThat(view.transitionAlpha).isEqualTo(1f)
+            assertThat(view.isAttachedToWindow).isFalse()
+            assertThat(view2.visibility).isEqualTo(View.VISIBLE)
+            assertThat(view2.transitionAlpha).isEqualTo(1f)
+            assertThat(view2.isAttachedToWindow).isTrue()
+        }
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun seekToScene_notSupportedTransition() {
+        class NoSeekingTransition : Fade() {
+            override fun isSeekingSupported(): Boolean = false
+        }
+        val scene1 = Scene(root, view)
+        val view2 = View(view.context)
+        val scene2 = Scene(root, view2)
+        rule.runOnUiThread {
+            TransitionManager.go(scene1)
+        }
+
+        rule.runOnUiThread {
+            TransitionManager.seekTo(scene2, NoSeekingTransition())
+        }
+    }
+
+    @Test
+    fun seekToScene_alreadyRunningTransition() {
+        val scene1 = Scene(root, view)
+        val view2 = View(view.context)
+        val scene2 = Scene(root, view2)
+        rule.runOnUiThread {
+            TransitionManager.go(scene1)
+        }
+
+        rule.runOnUiThread {
+            TransitionManager.go(scene2, Fade())
+            assertThat(TransitionManager.seekTo(scene1, Fade())).isNull()
+        }
+    }
 }
diff --git a/transition/transition/src/androidTest/java/androidx/transition/VelocityTracker1DTest.kt b/transition/transition/src/androidTest/java/androidx/transition/VelocityTracker1DTest.kt
new file mode 100644
index 0000000..d74c93e
--- /dev/null
+++ b/transition/transition/src/androidTest/java/androidx/transition/VelocityTracker1DTest.kt
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2023 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.transition
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.abs
+import org.junit.Test
+
+// Velocities between (1-Tolerance)*RV and (1+Tolerance)*RV are accepted
+// where RV is the "Real Velocity"
+private const val Tolerance: Float = 0.2f
+
+@SmallTest
+class VelocityTracker1DTest : BaseTest() {
+    @Test
+    fun twoPoints_nonDifferentialValues() {
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(1 to 5f, 2 to 15f),
+                expectedVelocity = 10000f
+            )
+        )
+    }
+
+    @Test
+    fun threePoints_pointerStoppedMoving_nonDifferentialValues() {
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    25 to 25f,
+                    50 to 50f,
+                    100 to 100f,
+                ),
+                // Expect 0 velocities, as the pointer will be considered to have stopped moving,
+                // due to the (100-50)=40ms gap from the last data point (i.e. it's effectively
+                // a data set with only 1 data point).
+                expectedVelocity = 0f,
+            )
+        )
+    }
+
+    /** Impulse strategy specific test cases. */
+    @Test
+    fun threePoints_zeroVelocity_nonDifferentialValues() {
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 273f,
+                    1 to 273f,
+                    2 to 273f,
+                ),
+                expectedVelocity = 0f
+            ),
+        )
+    }
+
+    @Test
+    fun resetTracking_defaultConstructor() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        val tracker = VelocityTracker1D()
+        tracker.addDataPoint(0, 0f)
+        tracker.addDataPoint(10, 5f)
+        tracker.addDataPoint(20, 10f)
+        tracker.addDataPoint(30, 15f)
+        tracker.addDataPoint(40, 30f)
+
+        tracker.resetTracking()
+
+        assertThat(tracker.calculateVelocity()).isZero()
+    }
+
+    @Test
+    fun resetTracking_nonDifferentialValues_impulse() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        val tracker = VelocityTracker1D()
+        tracker.addDataPoint(0, 0f)
+        tracker.addDataPoint(10, 5f)
+        tracker.addDataPoint(20, 10f)
+        tracker.addDataPoint(30, 15f)
+        tracker.addDataPoint(40, 30f)
+
+        tracker.resetTracking()
+
+        assertThat(tracker.calculateVelocity()).isZero()
+    }
+
+    @Test
+    fun linearMotion_positiveVelocity_positiveDataPoints_nonDifferentialValues() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 0f,
+                    10 to 5f,
+                    20 to 10f,
+                    30 to 15f,
+                ),
+                expectedVelocity = 500f,
+            )
+        )
+    }
+
+    @Test
+    fun linearMotion_positiveVelocity_negativeDataPoints_nonDifferentialValues() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to -20f,
+                    10 to -15f,
+                    20 to -10f,
+                ),
+                expectedVelocity = 500f,
+            )
+        )
+    }
+
+    @Test
+    fun linearMotion_positiveVelocity_mixedSignDataPoints_nonDifferentialValues() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to -5f,
+                    10 to 0f,
+                    20 to 5f,
+                ),
+                expectedVelocity = 500f,
+            )
+        )
+    }
+
+    @Test
+    fun linearMotion_negativeVelocity_negativeDataPoints_nonDifferentialValues() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 0f,
+                    10 to -5f,
+                    20 to -10f,
+                ),
+                expectedVelocity = -500f,
+            )
+        )
+    }
+
+    @Test
+    fun linearMotion_negativeVelocity_postiveDataPoints_nonDifferentialValues() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 20f,
+                    10 to 15f,
+                    20 to 10f,
+                ),
+                expectedVelocity = -500f,
+            )
+        )
+    }
+
+    @Test
+    fun linearMotion_negativeVelocity_mixedSignDataPoints_nonDifferentialValues() {
+        // Fixed velocity at 5 points per 10 milliseconds
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 5f,
+                    10 to 0f,
+                    20 to -5f,
+                ),
+                expectedVelocity = -500f,
+            )
+        )
+    }
+
+    @Test
+    fun linearHalfMotion() {
+        // Stay still for 50 ms, and then move 100 points in the final 50 ms.
+        // The final line is sloped at 2 units/ms.
+        // This can be visualized as 2 lines: flat line (50ms), and line with slope of 2 units/ms.
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 0f,
+                    10 to 0f,
+                    20 to 0f,
+                    30 to 0f,
+                    40 to 0f,
+                    50 to 0f,
+                    60 to 20f,
+                    70 to 40f,
+                    80 to 60f,
+                    90 to 80f,
+                    100 to 100f,
+                ),
+                expectedVelocity = 2000f
+            ),
+        )
+    }
+
+    @Test
+    fun linearHalfMotionSampled() {
+        // Linear half motion, but sampled much less frequently. The resulting velocity is higher
+        // than the previous test, because the path looks significantly different now if you
+        // were to just plot these points.
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 0f,
+                    30 to 0f,
+                    40 to 0f,
+                    70 to 40f,
+                    100 to 100f,
+                ),
+                expectedVelocity = 2018.2f
+            )
+        )
+    }
+
+    @Test
+    fun linearMotionFollowedByFlatLine() {
+        // Fixed velocity at first, but flat line afterwards.
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 0f,
+                    10 to 10f,
+                    20 to 20f,
+                    30 to 30f,
+                    40 to 40f,
+                    50 to 50f,
+                    60 to 50f,
+                    70 to 50f,
+                    80 to 50f,
+                    90 to 50f,
+                    100 to 50f,
+                ),
+                expectedVelocity = 1000f
+            )
+        )
+    }
+
+    @Test
+    fun linearMotionFollowedByFlatLineWithoutIntermediatePoints() {
+        // Fixed velocity at first, but flat line afterwards
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 0f,
+                    50 to 50f,
+                    100 to 50f,
+                ),
+                expectedVelocity = 0f
+            ),
+        )
+    }
+
+    @Test
+    fun swordfishFlingDown_xValues() {
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 271f,
+                    16 to 269.786346f,
+                    35 to 267.983063f,
+                    52 to 262.638397f,
+                    68 to 266.138824f,
+                    85 to 274.79245f,
+                    96 to 274.79245f,
+                ),
+                expectedVelocity = 623.57f
+            )
+        )
+    }
+
+    @Test
+    fun swordfishFlingDown_yValues() {
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    0 to 96f,
+                    16 to 106.922775f,
+                    35 to 156.660034f,
+                    52 to 220.339081f,
+                    68 to 331.581116f,
+                    85 to 428.113159f,
+                    96 to 428.113159f,
+                ),
+                expectedVelocity = 5970.73f
+            )
+        )
+    }
+
+    @Test
+    fun sailfishFlingUpSlow_xValues() {
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    235089067 to 528.0f,
+                    235089084 to 527.0f,
+                    235089093 to 527.0f,
+                    235089095 to 527.0f,
+                    235089101 to 527.0f,
+                    235089110 to 528.0f,
+                    235089112 to 528.25f,
+                    235089118 to 531.0f,
+                    235089126 to 535.0f,
+                    235089129 to 536.33f,
+                    235089135 to 540.0f,
+                    235089144 to 546.0f,
+                    235089146 to 547.21f,
+                    235089152 to 553.0f,
+                    235089160 to 559.0f,
+                    235089162 to 560.66f,
+                ),
+                expectedVelocity = 764.34f,
+            )
+        )
+    }
+
+    @Test
+    fun sailfishFlingUpSlow_yValues() {
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    235089067 to 983.0f,
+                    235089084 to 981.0f,
+                    235089093 to 977.0f,
+                    235089095 to 975.93f,
+                    235089101 to 970.0f,
+                    235089110 to 960.0f,
+                    235089112 to 957.51f,
+                    235089118 to 946.0f,
+                    235089126 to 931.0f,
+                    235089129 to 926.02f,
+                    235089135 to 914.0f,
+                    235089144 to 896.0f,
+                    235089146 to 892.36f,
+                    235089152 to 877.0f,
+                    235089160 to 851.0f,
+                    235089162 to 843.82f,
+                ),
+                expectedVelocity = -3604.82f,
+            )
+        )
+    }
+
+    @Test
+    fun sailfishFlingUpFast_xValues() {
+        // Some "repeated" data points are removed, since the conversion from ns to ms made some
+        // data ponits "repeated"
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    920922 to 561.0f,
+                    920930 to 559.0f,
+                    920938 to 559.0f,
+                    920947 to 562.91f,
+                    920955 to 577.0f,
+                    920963 to 596.87f,
+                    920972 to 631.0f,
+                    920980 to 671.31f,
+                    920989 to 715.0f,
+                ),
+                expectedVelocity = 5670.32f,
+            )
+        )
+    }
+
+    @Test
+    fun sailfishFlingUpFast_yValues() {
+        // Some "repeated" data points are removed, since the conversion from ns to ms made some
+        // data ponits "repeated"
+        checkTestCase(
+            VelocityTrackingTestCase(
+                dataPoints = listOf(
+                    920922 to 1412.0f,
+                    920930 to 1377.0f,
+                    920938 to 1371.0f,
+                    920947 to 1342.68f,
+                    920955 to 1272.0f,
+                    920963 to 1190.54f,
+                    920972 to 1093.0f,
+                    920980 to 994.68f,
+                    920989 to 903.0f,
+                ),
+                expectedVelocity = -13021.10f,
+            )
+        )
+    }
+
+    private fun checkTestCase(testCase: VelocityTrackingTestCase) {
+        val expectedVelocity = testCase.expectedVelocity
+        val tracker = VelocityTracker1D()
+        testCase.dataPoints.forEach {
+            tracker.addDataPoint(it.first.toLong(), it.second)
+        }
+
+        Truth.assertWithMessage(
+            "Wrong velocity for data points: ${testCase.dataPoints}" +
+                "\nExpected velocity: {$expectedVelocity}"
+        )
+            .that(tracker.calculateVelocity())
+            .isWithin(abs(expectedVelocity) * Tolerance)
+            .of(expectedVelocity)
+    }
+}
+
+/** Holds configs for a velocity tracking test case, for convenience. */
+private data class VelocityTrackingTestCase(
+    val dataPoints: List<Pair<Int, Float>>,
+    val expectedVelocity: Float
+)
diff --git a/transition/transition/src/main/java/androidx/transition/Transition.java b/transition/transition/src/main/java/androidx/transition/Transition.java
index c2c0986..9cdb387 100644
--- a/transition/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/transition/src/main/java/androidx/transition/Transition.java
@@ -22,7 +22,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -55,6 +54,10 @@
 import androidx.core.content.res.TypedArrayUtils;
 import androidx.core.util.Consumer;
 import androidx.core.view.ViewCompat;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatValueHolder;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1920,7 +1923,7 @@
             runAnimators();
         } else if (Build.VERSION.SDK_INT >= 34) {
             prepareAnimatorsForSeeking();
-            mSeekController.setCurrentPlayTimeMillis(0);
+            mSeekController.initPlayTime();
             mSeekController.ready();
         }
     }
@@ -2690,14 +2693,17 @@
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     class SeekController extends TransitionListenerAdapter implements TransitionSeekController,
-            ValueAnimator.AnimatorUpdateListener {
+            DynamicAnimation.OnAnimationUpdateListener {
+        // Animation calculations appear to work better with numbers that range greater than 1
         private long mCurrentPlayTime = -1;
         private ArrayList<Consumer<TransitionSeekController>> mOnReadyListeners = null;
+        private ArrayList<Consumer<TransitionSeekController>> mOnProgressListeners = null;
         private boolean mIsReady;
         private boolean mIsCanceled;
 
-        private ValueAnimator mAnimator;
-        private boolean mIsAnimatingReversed;
+        private SpringAnimation mSpringAnimation;
+        private Consumer<TransitionSeekController>[] mListenerCache = null;
+        private final VelocityTracker1D mVelocityTracker = new VelocityTracker1D();
 
         @Override
         public long getDurationMillis() {
@@ -2710,6 +2716,11 @@
         }
 
         @Override
+        public float getCurrentFraction() {
+            return ((float) getCurrentPlayTimeMillis()) / ((float) getDurationMillis());
+        }
+
+        @Override
         public boolean isReady() {
             return mIsReady;
         }
@@ -2723,34 +2734,54 @@
                     onReadyListeners.get(i).accept(this);
                 }
             }
+            callProgressListeners();
         }
 
         @Override
         public void setCurrentPlayTimeMillis(long playTimeMillis) {
-            if (mAnimator != null) {
+            if (mSpringAnimation != null) {
                 throw new IllegalStateException("setCurrentPlayTimeMillis() called after animation "
                         + "has been started");
             }
-            if (playTimeMillis == mCurrentPlayTime) {
+            if (playTimeMillis == mCurrentPlayTime || !isReady()) {
                 return; // no change
             }
 
+            long targetPlayTime = playTimeMillis;
             if (!mIsCanceled) {
-                if (playTimeMillis == 0 && mCurrentPlayTime > 0) {
+                if (targetPlayTime == 0 && mCurrentPlayTime > 0) {
                     // Force the transition to end
-                    playTimeMillis = -1;
+                    targetPlayTime = -1;
                 } else {
                     long duration = getDurationMillis();
                     // Force the transition to the end
-                    if (playTimeMillis == duration && mCurrentPlayTime < duration) {
-                        playTimeMillis = duration + 1;
+                    if (targetPlayTime == duration && mCurrentPlayTime < duration) {
+                        targetPlayTime = duration + 1;
                     }
                 }
-                if (playTimeMillis != mCurrentPlayTime) {
-                    Transition.this.setCurrentPlayTimeMillis(playTimeMillis, mCurrentPlayTime);
-                    mCurrentPlayTime = playTimeMillis;
+                if (targetPlayTime != mCurrentPlayTime) {
+                    Transition.this.setCurrentPlayTimeMillis(targetPlayTime, mCurrentPlayTime);
+                    mCurrentPlayTime = targetPlayTime;
                 }
             }
+            callProgressListeners();
+            mVelocityTracker.addDataPoint(AnimationUtils.currentAnimationTimeMillis(),
+                    (float) targetPlayTime);
+        }
+
+        void initPlayTime() {
+            long playTime = (getDurationMillis() == 0) ? 1 : 0;
+            Transition.this.setCurrentPlayTimeMillis(playTime, mCurrentPlayTime);
+            mCurrentPlayTime = playTime;
+        }
+
+        @Override
+        public void setCurrentFraction(float fraction) {
+            if (mSpringAnimation != null) {
+                throw new IllegalStateException("setCurrentFraction() called after animation "
+                        + "has been started");
+            }
+            setCurrentPlayTimeMillis((long) (fraction * getDurationMillis()));
         }
 
         @Override
@@ -2785,55 +2816,85 @@
         }
 
         @Override
-        public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
-            long time = Math.max(-1,
-                    Math.min(getDurationMillis() + 1, mAnimator.getCurrentPlayTime())
-            );
-            if (mIsAnimatingReversed) {
-                time = getDurationMillis() - time;
-            }
+        public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
+            long time = Math.max(-1, Math.min(getDurationMillis() + 1, Math.round((double) value)));
             Transition.this.setCurrentPlayTimeMillis(time, mCurrentPlayTime);
             mCurrentPlayTime = time;
+            callProgressListeners();
         }
 
-        private void createAnimator() {
-            long duration = getDurationMillis() + 1;
-            mAnimator = ValueAnimator.ofInt((int) duration);
-            mAnimator.setInterpolator(null);
-            mAnimator.setDuration(duration);
-            mAnimator.addUpdateListener(this);
+        private void ensureAnimation() {
+            if (mSpringAnimation != null) {
+                return;
+            }
+            mVelocityTracker.addDataPoint(AnimationUtils.currentAnimationTimeMillis(),
+                    (float) mCurrentPlayTime);
+            mSpringAnimation = new SpringAnimation(new FloatValueHolder());
+            SpringForce springForce = new SpringForce();
+            springForce.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
+            springForce.setStiffness(SpringForce.STIFFNESS_LOW);
+            mSpringAnimation.setSpring(springForce);
+            mSpringAnimation.setStartValue((float) mCurrentPlayTime);
+            mSpringAnimation.addUpdateListener(this);
+            mSpringAnimation.setStartVelocity(mVelocityTracker.calculateVelocity());
+            mSpringAnimation.setMaxValue((float) (getDurationMillis() + 1));
+            mSpringAnimation.setMinValue(-1f);
+            mSpringAnimation.setMinimumVisibleChange(4f); // 4 milliseconds ~ 1/2 frame @ 120Hz
+            mSpringAnimation.addEndListener((anim, canceled, value, velocity) -> {
+                if (!canceled) {
+                    boolean isReversed = value < 1f;
+                    notifyListeners(TransitionNotification.ON_END, isReversed);
+                }
+                mSpringAnimation = null;
+            });
         }
 
         @Override
         public void animateToEnd() {
-            if (mAnimator != null) {
-                mAnimator.cancel();
-            }
-            final long duration = getDurationMillis();
-            if (mCurrentPlayTime > duration) {
-                return; // we're already at the end
-            }
-            createAnimator();
-            mIsAnimatingReversed = false;
-            mAnimator.setCurrentPlayTime(mCurrentPlayTime);
-            mAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    notifyListeners(TransitionNotification.ON_END, false);
-                }
-            });
-            mAnimator.start();
+            ensureAnimation();
+            mSpringAnimation.animateToFinalPosition((float) (getDurationMillis() + 1));
         }
 
         @Override
         public void animateToStart() {
-            if (mAnimator != null) {
-                mAnimator.cancel();
+            ensureAnimation();
+            mSpringAnimation.animateToFinalPosition(-1);
+        }
+
+        @Override
+        public void addOnProgressChangedListener(
+                @NonNull Consumer<TransitionSeekController> consumer) {
+            if (mOnProgressListeners == null) {
+                mOnProgressListeners = new ArrayList<>();
             }
-            createAnimator();
-            mAnimator.setCurrentPlayTime(getDurationMillis() - mCurrentPlayTime);
-            mIsAnimatingReversed = true;
-            mAnimator.start();
+            mOnProgressListeners.add(consumer);
+        }
+
+        @Override
+        public void removeOnProgressChangedListener(
+                @NonNull Consumer<TransitionSeekController> consumer) {
+            if (mOnProgressListeners != null) {
+                mOnProgressListeners.remove(consumer);
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        private void callProgressListeners() {
+            if (mOnProgressListeners == null || mOnProgressListeners.isEmpty()) {
+                return;
+            }
+            int size = mOnProgressListeners.size();
+            if (mListenerCache == null) {
+                mListenerCache = new Consumer[size];
+            }
+            Consumer<TransitionSeekController>[] cache =
+                    mOnProgressListeners.toArray(mListenerCache);
+            mListenerCache = null;
+            for (int i = 0; i < size; i++) {
+                cache[i].accept(this);
+                cache[i] = null;
+            }
+            mListenerCache = cache;
         }
     }
 }
diff --git a/transition/transition/src/main/java/androidx/transition/TransitionManager.java b/transition/transition/src/main/java/androidx/transition/TransitionManager.java
index 62ee14c..2fdb6d5 100644
--- a/transition/transition/src/main/java/androidx/transition/TransitionManager.java
+++ b/transition/transition/src/main/java/androidx/transition/TransitionManager.java
@@ -350,6 +350,59 @@
     }
 
     /**
+     * Convenience method to seek to the given scene using the given transition. If seeking
+     * is not supported because the device is {@link Build.VERSION_CODES.TIRAMISU} or earlier,
+     * the scene transition is immediate and {@code null} is returned.
+     *
+     * @param scene      The Scene to change to
+     * @param transition The transition to use for this scene change.
+     * @return a {@link TransitionSeekController} that can be used control the animation to the
+     * destination scene. {@code null} is returned when seeking is not supported on the scene,
+     * either because it is running on {@link android.os.Build.VERSION_CODES.TIRAMISU} or earlier,
+     * another Transition is being captured for {@code sceneRoot}, or {@code sceneRoot} hasn't
+     * had a layout yet.
+     * @throws IllegalArgumentException if {@code transition} returns {@code false} from
+     * {@link Transition#isSeekingSupported()}.
+     */
+    @Nullable
+    public static TransitionSeekController seekTo(
+            @NonNull Scene scene,
+            @NonNull Transition transition
+    ) {
+        final ViewGroup sceneRoot = scene.getSceneRoot();
+
+        if (!transition.isSeekingSupported()) {
+            throw new IllegalArgumentException("The Transition must support seeking.");
+        }
+        if (sPendingTransitions.contains(sceneRoot)) {
+            return null; // Already in the process of transitioning
+        }
+        Scene oldScene = Scene.getCurrentScene(sceneRoot);
+        if (!ViewCompat.isLaidOut(sceneRoot)
+                || Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+        ) {
+            // Can't control it, so just change the scene immediately
+            if (oldScene != null) {
+                oldScene.exit();
+            }
+            scene.enter();
+            return null;
+        }
+        sPendingTransitions.add(sceneRoot);
+        final Transition transitionClone = transition.clone();
+        final TransitionSet set = new TransitionSet();
+        set.addTransition(transitionClone);
+        if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
+            set.setCanRemoveViews(true);
+        }
+        sceneChangeSetup(sceneRoot, set);
+        scene.enter();
+
+        sceneChangeRunTransition(sceneRoot, set);
+        return set.createSeekController();
+    }
+
+    /**
      * Convenience method to simply change to the given scene using
      * the given transition.
      *
diff --git a/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java b/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java
index bcede11..f9b00c5 100644
--- a/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java
+++ b/transition/transition/src/main/java/androidx/transition/TransitionSeekController.java
@@ -18,6 +18,8 @@
 
 import android.view.ViewGroup;
 
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.core.util.Consumer;
 
@@ -31,15 +33,24 @@
     /**
      * @return The total duration, in milliseconds, of the Transition's animations.
      */
+    @IntRange(from = 0)
     long getDurationMillis();
 
     /**
      * @return The time, in milliseconds, of the animation. This will be between 0
      * and {@link #getDurationMillis()}.
      */
+    @IntRange(from = 0)
     long getCurrentPlayTimeMillis();
 
     /**
+     * @return The fraction, between 0 and 1, of the progress of the transition.
+     * @see #getCurrentPlayTimeMillis()
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    float getCurrentFraction();
+
+    /**
      * Returns {@code true} when the Transition is ready to seek or {@code false}
      * when the Transition's animations have yet to be built.
      */
@@ -73,14 +84,26 @@
     void animateToEnd();
 
     /**
+     * Sets the position of the Transition's animation. {@code fraction} should be
+     * between 0 and 1, inclusive, where 0 indicates that the transition hasn't progressed and 1
+     * indicates that the transition is completed. Calling this before {@link #isReady()} is
+     * {@code true} will do nothing.
+     *
+     * @param fraction The fraction, between 0 and 1, inclusive, of the progress of the transition.
+     * @see #setCurrentPlayTimeMillis(long)
+     */
+    void setCurrentFraction(@FloatRange(from = 0.0, to = 1.0) float fraction);
+
+    /**
      * Sets the position of the Transition's animation. {@code playTimeMillis} should be
-     * between 0 and {@link #getDurationMillis()}. This should not be called when
-     * {@link #isReady()} is {@code false}.
+     * between 0 and {@link #getDurationMillis()}. Calling this before {@link #isReady()} is
+     * {@code true} will do nothing.
      *
      * @param playTimeMillis The time, between 0 and {@link #getDurationMillis()} that the
      *                       animation should play.
+     * @see #setCurrentFraction(float)
      */
-    void setCurrentPlayTimeMillis(long playTimeMillis);
+    void setCurrentPlayTimeMillis(@IntRange(from = 0) long playTimeMillis);
 
     /**
      * Adds a listener to know when {@link #isReady()} is {@code true}. The listener will
@@ -98,5 +121,20 @@
      * @param onReadyListener The listener to be removed so that it won't be notified when ready.
      */
     void removeOnReadyListener(@NonNull Consumer<TransitionSeekController> onReadyListener);
+
+    /**
+     * Add a listener for whenever the progress of the transition is changed. This will be called
+     * when {@link #setCurrentPlayTimeMillis(long)} or {@link #setCurrentFraction(float)} are
+     * called as well as when the animation from {@link #animateToEnd()} or
+     * {@link #animateToStart()} changes the progress.
+     * @param consumer A method that accepts this TransitionSeekController.
+     */
+    void addOnProgressChangedListener(@NonNull Consumer<TransitionSeekController> consumer);
+
+    /**
+     * Remove a listener previously added in {@link #addOnProgressChangedListener(Consumer)}\
+     * @param consumer The listener to be removed.
+     */
+    void removeOnProgressChangedListener(@NonNull Consumer<TransitionSeekController> consumer);
 }
 
diff --git a/transition/transition/src/main/java/androidx/transition/VelocityTracker1D.java b/transition/transition/src/main/java/androidx/transition/VelocityTracker1D.java
new file mode 100644
index 0000000..e4e7ccb
--- /dev/null
+++ b/transition/transition/src/main/java/androidx/transition/VelocityTracker1D.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2023 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.transition;
+
+import java.util.Arrays;
+
+/**
+ * Velocity Tracker, simplified from compose's VelocityTracker1D.
+ */
+class VelocityTracker1D {
+    private static final int HISTORY_SIZE = 20;
+    private static final int ASSUME_POINTER_MOVE_STOPPED_MILLIS = 40;
+    private static final int HORIZON_MILLIS = 100;
+
+    // Circular buffer; current sample at index.
+    private long[] mTimeSamples = new long[HISTORY_SIZE];
+    private float[] mDataSamples = new float[HISTORY_SIZE];
+    private int mIndex = 0;
+
+    VelocityTracker1D() {
+        Arrays.fill(mTimeSamples, Long.MIN_VALUE);
+    }
+
+    /**
+     * Adds a data point for velocity calculation at a given time, {@code timeMillis}. The data
+     * point represents an absolute position.
+     * <p>
+     * Use the same units for the data points provided. For example, having some data points in `cm`
+     * and some in `m` will result in incorrect velocity calculations, as this method (and the
+     * tracker) has no knowledge of the units used.
+     */
+    public void addDataPoint(long timeMillis, float data) {
+        mIndex = (mIndex + 1) % HISTORY_SIZE;
+        mTimeSamples[mIndex] = timeMillis;
+        mDataSamples[mIndex] = data;
+    }
+
+    public void resetTracking() {
+        mIndex = 0;
+        Arrays.fill(mTimeSamples, Long.MIN_VALUE);
+        Arrays.fill(mDataSamples, 0f);
+    }
+
+    /**
+     * Computes the estimated velocity at the time of the last provided data point. The units of
+     * velocity will be `units/second`, where `units` is the units of the data points provided via
+     * [addDataPoint].
+     *
+     * This can be expensive. Only call this when you need the velocity.
+     */
+    float calculateVelocity() {
+        int sampleCount = 0;
+        int index = mIndex;
+
+        if (index == 0 && mTimeSamples[index] == Long.MIN_VALUE) {
+            return 0f; // We haven't received any data
+        }
+
+        // The sample at index is our newest sample.  If it is null, we have no samples so return.
+        long newestTime = mTimeSamples[index];
+
+        long previousTime = newestTime;
+
+        // Starting with the most recent sample, iterate backwards while
+        // the samples represent continuous motion.
+        do {
+            long sampleTime = mTimeSamples[index];
+            if (sampleTime == Long.MIN_VALUE) {
+                break; // no point here
+            }
+            float age = newestTime - sampleTime;
+            float delta = Math.abs(sampleTime - previousTime);
+            previousTime = sampleTime;
+
+            if (age > HORIZON_MILLIS || delta > ASSUME_POINTER_MOVE_STOPPED_MILLIS) {
+                break;
+            }
+
+            index = (index == 0 ? HISTORY_SIZE : index) - 1;
+            sampleCount++;
+        } while (sampleCount < HISTORY_SIZE);
+
+        if (sampleCount < 2) {
+            return 0f; // Not enough data to have a velocity
+        }
+
+        if (sampleCount == 2) {
+            // Simple diff in time
+            int prevIndex = mIndex == 0 ? HISTORY_SIZE - 1 : mIndex - 1;
+            float timeDiff = mTimeSamples[mIndex] - mTimeSamples[prevIndex];
+            if (timeDiff == 0f) {
+                return 0f;
+            }
+            float dataDiff = mDataSamples[mIndex] - mDataSamples[prevIndex];
+            return dataDiff / timeDiff * 1000;
+        }
+
+        float work = 0f;
+        int startIndex = (mIndex - sampleCount + HISTORY_SIZE + 1) % HISTORY_SIZE;
+        int endIndex = (mIndex + 1 + HISTORY_SIZE) % HISTORY_SIZE;
+        previousTime = mTimeSamples[startIndex];
+        float previousData = mDataSamples[startIndex];
+        for (int i = (startIndex + 1) % HISTORY_SIZE; i != endIndex; i = (i + 1) % HISTORY_SIZE) {
+            long time = mTimeSamples[i];
+            long timeDelta = time - previousTime;
+            if (timeDelta == 0f) {
+                continue;
+            }
+            float data = mDataSamples[i];
+            float vPrev = kineticEnergyToVelocity(work);
+            float dataPointsDelta = data - previousData;
+
+            float vCurr = dataPointsDelta / timeDelta;
+            work += (vCurr - vPrev) * Math.abs(vCurr);
+            if (i == startIndex + 1) {
+                work = (work * 0.5f);
+            }
+            previousTime = time;
+            previousData = data;
+        }
+        return kineticEnergyToVelocity(work) * 1000;
+    }
+
+    private float kineticEnergyToVelocity(float kineticEnergy) {
+        return (float) (Math.signum(kineticEnergy) * Math.sqrt(2 * Math.abs(kineticEnergy)));
+    }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CardDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CardDemo.kt
new file mode 100644
index 0000000..45c3ad7
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/CardDemo.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 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.compose.material3.demos
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.material3.AppCard
+import androidx.wear.compose.material3.CardDefaults
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.samples.AppCardSample
+import androidx.wear.compose.material3.samples.AppCardWithIconSample
+import androidx.wear.compose.material3.samples.CardSample
+import androidx.wear.compose.material3.samples.OutlinedAppCardSample
+import androidx.wear.compose.material3.samples.OutlinedCardSample
+import androidx.wear.compose.material3.samples.OutlinedTitleCardSample
+import androidx.wear.compose.material3.samples.R
+import androidx.wear.compose.material3.samples.TitleCardSample
+import androidx.wear.compose.material3.samples.TitleCardWithImageSample
+
+@Composable
+fun CardDemo() {
+    ScalingLazyColumn(
+        modifier = Modifier.fillMaxSize(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        item { ListHeader { Text("Card") } }
+        item { CardSample() }
+        item { OutlinedCardSample() }
+
+        item { ListHeader { Text("App card") } }
+        item { AppCardSample() }
+        item { AppCardWithIconSample() }
+        item { OutlinedAppCardSample() }
+
+        item { ListHeader { Text("Title card") } }
+        item { TitleCardSample() }
+        item { OutlinedTitleCardSample() }
+
+        item { ListHeader { Text("Image card") } }
+        item {
+            AppCard(
+                onClick = { /* Do something */ },
+                appName = { Text("App name") },
+                appImage = {
+                    Icon(
+                        Icons.Filled.Favorite,
+                        contentDescription = "favourites",
+                        modifier = Modifier.size(CardDefaults.AppImageSize)
+                    )
+                },
+                title = { Text("Card title") },
+                time = { Text("now") },
+                colors = CardDefaults.imageCardColors(
+                    containerPainter = CardDefaults.imageWithScrimBackgroundPainter(
+                        backgroundImagePainter = painterResource(id = R.drawable.backgroundimage)
+                    ),
+                    contentColor = MaterialTheme.colorScheme.onSurface,
+                    titleColor = MaterialTheme.colorScheme.onSurface
+                ),
+                modifier = Modifier.semantics { contentDescription = "Background image" }
+            ) {
+                Text("Card content")
+            }
+        }
+        item { TitleCardWithImageSample() }
+    }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index 13e7784..c239cfe 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -16,27 +16,16 @@
 
 package androidx.wear.compose.material3.demos
 
-import androidx.compose.ui.Alignment
-import androidx.wear.compose.foundation.lazy.AutoCenteringParams
-import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.integration.demos.common.Centralize
 import androidx.wear.compose.integration.demos.common.ComposableDemo
 import androidx.wear.compose.integration.demos.common.DemoCategory
-import androidx.wear.compose.material3.samples.AppCardSample
-import androidx.wear.compose.material3.samples.AppCardWithIconSample
-import androidx.wear.compose.material3.samples.CardSample
 import androidx.wear.compose.material3.samples.EdgeSwipeForSwipeToDismiss
 import androidx.wear.compose.material3.samples.FixedFontSize
-import androidx.wear.compose.material3.samples.OutlinedAppCardSample
-import androidx.wear.compose.material3.samples.OutlinedCardSample
-import androidx.wear.compose.material3.samples.OutlinedTitleCardSample
 import androidx.wear.compose.material3.samples.SimpleSwipeToDismissBox
 import androidx.wear.compose.material3.samples.StatefulSwipeToDismissBox
 import androidx.wear.compose.material3.samples.StepperSample
 import androidx.wear.compose.material3.samples.StepperWithIntegerSample
 import androidx.wear.compose.material3.samples.StepperWithRangeSemanticsSample
-import androidx.wear.compose.material3.samples.TitleCardSample
-import androidx.wear.compose.material3.samples.TitleCardWithImageSample
 
 val WearMaterial3Demos = DemoCategory(
     "Material 3",
@@ -64,26 +53,9 @@
                 },
             )
         ),
-        DemoCategory(
-            "Card",
-            listOf(
-                ComposableDemo("Samples") {
-                    ScalingLazyColumn(
-                        horizontalAlignment = Alignment.CenterHorizontally,
-                        autoCentering = AutoCenteringParams(itemIndex = 0)
-                    ) {
-                        item { CardSample() }
-                        item { AppCardSample() }
-                        item { AppCardWithIconSample() }
-                        item { TitleCardSample() }
-                        item { TitleCardWithImageSample() }
-                        item { OutlinedCardSample() }
-                        item { OutlinedAppCardSample() }
-                        item { OutlinedTitleCardSample() }
-                    }
-                }
-            )
-        ),
+        ComposableDemo("Card") {
+            CardDemo()
+        },
         ComposableDemo("Text Button") {
             TextButtonDemo()
         },
@@ -113,7 +85,7 @@
             "Slider",
             SliderDemos
         ),
-        ComposableDemo("List Headers") {
+        ComposableDemo("List Header") {
             Centralize {
                 ListHeaderDemo()
             }
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt
index 4e5af0f..17424be 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/CardSample.kt
@@ -19,10 +19,14 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
 import androidx.wear.compose.material3.AppCard
 import androidx.wear.compose.material3.Card
 import androidx.wear.compose.material3.CardDefaults
@@ -47,8 +51,8 @@
 fun AppCardSample() {
     AppCard(
         onClick = { /* Do something */ },
-        appName = { Text("AppName") },
-        title = { Text("AppCard") },
+        appName = { Text("App name") },
+        title = { Text("Card title") },
         time = { Text("now") },
     ) {
         Text("Card content")
@@ -60,7 +64,7 @@
 fun AppCardWithIconSample() {
     AppCard(
         onClick = { /* Do something */ },
-        appName = { Text("AppName") },
+        appName = { Text("App name") },
         appImage = {
             Icon(
                 painter = painterResource(id = android.R.drawable.star_big_off),
@@ -70,7 +74,7 @@
                     .wrapContentSize(align = Alignment.Center),
             )
         },
-        title = { Text("AppCard with icon") },
+        title = { Text("Card title") },
         time = { Text("now") },
     ) {
         Text("Card content")
@@ -82,7 +86,7 @@
 fun TitleCardSample() {
     TitleCard(
         onClick = { /* Do something */ },
-        title = { Text("TitleCard") },
+        title = { Text("Title card") },
         time = { Text("now") },
     ) {
         Text("Card content")
@@ -94,14 +98,16 @@
 fun TitleCardWithImageSample() {
     TitleCard(
         onClick = { /* Do something */ },
-        title = { Text("TitleCard With an ImageBackground") },
+        title = { Text("Card title") },
+        time = { Text("now") },
         colors = CardDefaults.imageCardColors(
             containerPainter = CardDefaults.imageWithScrimBackgroundPainter(
                 backgroundImagePainter = painterResource(id = R.drawable.backgroundimage)
             ),
             contentColor = MaterialTheme.colorScheme.onSurface,
             titleColor = MaterialTheme.colorScheme.onSurface
-        )
+        ),
+        modifier = Modifier.semantics { contentDescription = "Background image" }
     ) {
         Text("Card content")
     }
@@ -113,7 +119,7 @@
     OutlinedCard(
         onClick = { /* Do something */ },
     ) {
-        Text("OutlinedCard")
+        Text("Outlined card")
     }
 }
 
@@ -122,8 +128,16 @@
 fun OutlinedAppCardSample() {
     AppCard(
         onClick = { /* Do something */ },
-        appName = { Text("AppName") },
-        title = { Text("Outlined AppCard") },
+        appName = { Text("App name") },
+        appImage = {
+            Icon(
+                Icons.Filled.Favorite,
+                contentDescription = "favourites",
+                modifier = Modifier.size(CardDefaults.AppImageSize)
+            )
+        },
+        title = { Text("App card") },
+        time = { Text("now") },
         colors = CardDefaults.outlinedCardColors(),
         border = CardDefaults.outlinedCardBorder(),
     ) {
@@ -136,7 +150,8 @@
 fun OutlinedTitleCardSample() {
     TitleCard(
         onClick = { /* Do something */ },
-        title = { Text("Outlined TitleCard") },
+        title = { Text("Title card") },
+        time = { Text("now") },
         colors = CardDefaults.outlinedCardColors(),
         border = CardDefaults.outlinedCardBorder(),
     ) {
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt
index ef263e8..3b32885 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ListHeaderSample.kt
@@ -29,7 +29,7 @@
 @Composable
 fun ListHeaderSample() {
     ListHeader {
-        Text("List Header")
+        Text("Header")
     }
 }
 
@@ -37,7 +37,7 @@
 @Composable
 fun ListSubheaderSample() {
     ListSubheader {
-        Text("List Subheader")
+        Text("Subheader")
     }
 }
 
@@ -45,7 +45,7 @@
 @Composable
 fun ListSubheaderWithIconSample() {
     ListSubheader(
-        label = { Text(text = "List Subheader") },
+        label = { Text(text = "Subheader") },
         icon = { Icon(imageVector = Icons.Outlined.Home, "home") }
     )
 }
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
index 8dd28b2..2109c6a 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
@@ -158,7 +158,7 @@
                 )
         ) {
             val modifier = Modifier.height(LocalConfiguration.current.screenHeightDp.dp / 2)
-            repeat(3) { i ->
+            repeat(10) { i ->
                 ExampleCard(modifier, i)
             }
         }
@@ -192,7 +192,7 @@
                 focusRequester = focusRequester
             )
         ) {
-            items(5) { i ->
+            items(10) { i ->
                 val modifier = Modifier.fillParentMaxHeight(0.5f)
                 ExampleCard(modifier = modifier, i = i)
             }
@@ -235,7 +235,7 @@
                 ListHeader { Text("Cards") }
             }
 
-            items(5) { i ->
+            items(10) { i ->
                 ExampleCard(Modifier.fillParentMaxHeight(0.5f), i)
             }
         }
@@ -276,7 +276,7 @@
                 ListHeader { Text("Chips") }
             }
 
-            items(5) { i ->
+            items(10) { i ->
                 ExampleChip(Modifier.fillMaxWidth(), i)
             }
         }
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java
index 906634e..65f4fa2 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDiffer.java
@@ -272,13 +272,24 @@
         return new LayoutDiff(changedNodes);
     }
 
-    /** Check whether 2 nodes represented by the given fingerprints are equivalent. */
+    /** Check whether two nodes represented by the given fingerprints are equivalent. */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public static boolean areNodesEquivalent(
             @NonNull NodeFingerprint nodeA, @NonNull NodeFingerprint nodeB) {
         return getChangeType(nodeA, nodeB) == NodeChangeType.NO_CHANGE;
     }
 
+    /** Check whether two {@link TreeFingerprint} objects are equivalent. */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public static boolean areSameFingerprints(
+            @NonNull TreeFingerprint first, @NonNull TreeFingerprint second) {
+        NodeFingerprint prev = first.getRoot();
+        NodeFingerprint current = second.getRoot();
+        return current.getSelfTypeValue() == prev.getSelfTypeValue()
+                && current.getSelfPropsValue() == prev.getSelfPropsValue()
+                && current.getChildNodesValue() == prev.getChildNodesValue();
+    }
+
     private static void addChangedNodes(
             @NonNull NodeFingerprint prevNodeFingerprint,
             @NonNull TreeNode node,
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
index 5f53a02..1762d0b 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 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.
@@ -39,12 +39,16 @@
 import androidx.wear.protolayout.expression.pipeline.FixedQuotaManagerImpl;
 import androidx.wear.protolayout.expression.pipeline.PlatformDataProvider;
 import androidx.wear.protolayout.expression.pipeline.StateStore;
+import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement;
+import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement.InnerCase;
 import androidx.wear.protolayout.proto.LayoutElementProto.Layout;
+import androidx.wear.protolayout.proto.LayoutElementProto.LayoutElement;
 import androidx.wear.protolayout.proto.ResourceProto;
 import androidx.wear.protolayout.proto.StateProto.State;
 import androidx.wear.protolayout.renderer.ProtoLayoutExtensionViewProvider;
 import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
 import androidx.wear.protolayout.renderer.ProtoLayoutVisibilityState;
+import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer;
 import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
 import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater;
 import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater.InflateResult;
@@ -55,12 +59,14 @@
 import androidx.wear.protolayout.renderer.inflater.ResourceResolvers;
 import androidx.wear.protolayout.renderer.inflater.StandardResourceResolvers;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.SettableFuture;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
@@ -89,7 +95,7 @@
     }
 
     private static final int DEFAULT_MAX_CONCURRENT_RUNNING_ANIMATIONS = 4;
-
+    static final int MAX_LAYOUT_ELEMENT_DEPTH = 30;
     @NonNull private static final String TAG = "ProtoLayoutViewInstance";
 
     @NonNull private final Context mUiContext;
@@ -134,6 +140,12 @@
     @Nullable private Layout mPrevLayout = null;
 
     /**
+     * This field is used to avoid unnecessarily checking layout depth if the layout was previously
+     * failing the check.
+     */
+    private boolean mPrevLayoutAlreadyFailingDepthCheck = false;
+
+    /**
      * This is used as the Future for the currently running inflation session. The first time
      * "attach" is called, it should start the renderer. Subsequent attach calls should only ever
      * re-attach "inflateParent".
@@ -703,6 +715,21 @@
             return new FailedRenderResult();
         }
 
+        boolean sameFingerprint =
+                prevRenderedMetadata != null
+                        && ProtoLayoutDiffer.areSameFingerprints(
+                                prevRenderedMetadata.getTreeFingerprint(), layout.getFingerprint());
+
+        if (sameFingerprint) {
+            if (mPrevLayoutAlreadyFailingDepthCheck) {
+                throwExceptionForLayoutDepthCheckFailure();
+            }
+        } else {
+            checkLayoutDepth(layout.getRoot(), MAX_LAYOUT_ELEMENT_DEPTH);
+        }
+
+        mPrevLayoutAlreadyFailingDepthCheck = false;
+
         ProtoLayoutInflater.Config.Builder inflaterConfigBuilder =
                 new ProtoLayoutInflater.Config.Builder(mUiContext, layout, resolvers)
                         .setLoadActionExecutor(mUiExecutorService)
@@ -1059,6 +1086,52 @@
         }
     }
 
+    /** Returns true if the layout element depth doesn't exceed the given {@code allowedDepth}. */
+    private void checkLayoutDepth(LayoutElement layoutElement, int allowedDepth) {
+        if (allowedDepth <= 0) {
+            throwExceptionForLayoutDepthCheckFailure();
+        }
+        List<LayoutElement> children = ImmutableList.of();
+        switch (layoutElement.getInnerCase()) {
+            case COLUMN:
+                children = layoutElement.getColumn().getContentsList();
+                break;
+            case ROW:
+                children = layoutElement.getRow().getContentsList();
+                break;
+            case BOX:
+                children = layoutElement.getBox().getContentsList();
+                break;
+            case ARC:
+                List<ArcLayoutElement> arcElements = layoutElement.getArc().getContentsList();
+                if (!arcElements.isEmpty() && allowedDepth == 1) {
+                    throwExceptionForLayoutDepthCheckFailure();
+                }
+                for (ArcLayoutElement element : arcElements) {
+                    if (element.getInnerCase() == InnerCase.ADAPTER) {
+                        checkLayoutDepth(element.getAdapter().getContent(), allowedDepth - 1);
+                    }
+                }
+                break;
+            case SPANNABLE:
+                if (layoutElement.getSpannable().getSpansCount() > 0 && allowedDepth == 1) {
+                    throwExceptionForLayoutDepthCheckFailure();
+                }
+                break;
+            default:
+                // Other LayoutElements have depth of one.
+        }
+        for (LayoutElement child : children) {
+            checkLayoutDepth(child, allowedDepth - 1);
+        }
+    }
+
+    private void throwExceptionForLayoutDepthCheckFailure() {
+        mPrevLayoutAlreadyFailingDepthCheck = true;
+        throw new IllegalStateException(
+                "Layout depth exceeds maximum allowed depth: " + MAX_LAYOUT_ELEMENT_DEPTH);
+    }
+
     @Override
     public void close() throws Exception {
         detachInternal();
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java
index 11cbf345..5b6bdc5 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/RenderedMetadata.java
@@ -43,7 +43,7 @@
     }
 
     @NonNull
-    TreeFingerprint getTreeFingerprint() {
+    public TreeFingerprint getTreeFingerprint() {
         return mTreeFingerprint;
     }
 
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java
index b86f294..deb6dbc 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/common/ProtoLayoutDifferTest.java
@@ -303,6 +303,30 @@
     }
 
     @Test
+    public void areSameFingerprints() {
+        assertThat(
+                        ProtoLayoutDiffer.areSameFingerprints(
+                                referenceLayout().getFingerprint(),
+                                referenceLayout().getFingerprint()))
+                .isTrue();
+        assertThat(
+                        ProtoLayoutDiffer.areSameFingerprints(
+                                referenceLayout().getFingerprint(),
+                                layoutWithOneUpdatedNode().getFingerprint()))
+                .isFalse();
+        assertThat(
+                        ProtoLayoutDiffer.areSameFingerprints(
+                                referenceLayout().getFingerprint(),
+                                layoutWithDifferentNumberOfChildren().getFingerprint()))
+                .isFalse();
+        assertThat(
+                        ProtoLayoutDiffer.areSameFingerprints(
+                                referenceLayout().getFingerprint(),
+                                layoutWithUpdateToNodeSelfFingerprint().getFingerprint()))
+                .isFalse();
+    }
+
+    @Test
     public void isChildOf_forAnActualChild_returnsTrue() {
         String childPosId = "pT1.2.3";
         String parentPosId = "pT1.2";
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java
index b158c38..9f7018ae 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/helper/TestDsl.java
@@ -37,6 +37,7 @@
 import androidx.wear.protolayout.proto.FingerprintProto.TreeFingerprint;
 import androidx.wear.protolayout.proto.LayoutElementProto;
 import androidx.wear.protolayout.proto.LayoutElementProto.Arc;
+import androidx.wear.protolayout.proto.LayoutElementProto.ArcAdapter;
 import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement;
 import androidx.wear.protolayout.proto.LayoutElementProto.ArcText;
 import androidx.wear.protolayout.proto.LayoutElementProto.Box;
@@ -433,6 +434,10 @@
         return arcInternal(/* propsConsumer= */ null, nodes);
     }
 
+    public static LayoutNode arcAdapter(LayoutNode layoutNode) {
+        return arcAdapterInternal(layoutNode);
+    }
+
     private static LayoutNode arcInternal(
             @Nullable Consumer<ArcProps> propsConsumer, LayoutNode... nodes) {
         LayoutNode element = new LayoutNode();
@@ -449,6 +454,15 @@
         return element;
     }
 
+    private static LayoutNode arcAdapterInternal(LayoutNode node) {
+        LayoutNode element = new LayoutNode();
+        ArcAdapter.Builder builder = ArcAdapter.newBuilder().setContent(node.mLayoutElement);
+        int selfPropsFingerprint = 0;
+        element.mArcLayoutElement = ArcLayoutElement.newBuilder().setAdapter(builder.build());
+        element.mFingerprint = fingerprint("arcAdapter", selfPropsFingerprint, node);
+        return element;
+    }
+
     public static LayoutNode arcText(String text) {
         LayoutNode element = new LayoutNode();
         element.mArcLayoutElement =
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
index ca3f94e..3ad5656 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
@@ -17,10 +17,16 @@
 package androidx.wear.protolayout.renderer.impl;
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.arc;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.arcAdapter;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.box;
 import static androidx.wear.protolayout.renderer.helper.TestDsl.column;
 import static androidx.wear.protolayout.renderer.helper.TestDsl.dynamicFixedText;
 import static androidx.wear.protolayout.renderer.helper.TestDsl.layout;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.spanText;
+import static androidx.wear.protolayout.renderer.helper.TestDsl.spannable;
 import static androidx.wear.protolayout.renderer.helper.TestDsl.text;
+import static androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.MAX_LAYOUT_ELEMENT_DEPTH;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -39,6 +45,7 @@
 import androidx.wear.protolayout.expression.pipeline.StateStore;
 import androidx.wear.protolayout.proto.LayoutElementProto.Layout;
 import androidx.wear.protolayout.proto.ResourceProto.Resources;
+import androidx.wear.protolayout.renderer.helper.TestDsl.LayoutNode;
 import androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config;
 
 import com.google.common.collect.ImmutableList;
@@ -55,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -390,6 +398,107 @@
         assertThat(mRootContainer.getChildCount()).isEqualTo(0);
     }
 
+    @Test
+    public void layoutDepthExceedsMaximumDepth_renderingFail() throws Exception {
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+        assertThrows(
+                ExecutionException.class,
+                () -> renderAndAttachLayout(layout(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH + 1))));
+    }
+
+    @Test
+    public void layoutDepthIsEqualToMaximumDepth_renderingPass() throws Exception {
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+
+        LayoutNode[] children = new LayoutNode[MAX_LAYOUT_ELEMENT_DEPTH];
+        for (int i = 0; i < children.length; i++) {
+            children[i] = recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH - 1);
+        }
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        // MAX_LAYOUT_ELEMENT_DEPTH branches of depth MAX_LAYOUT_ELEMENT_DEPTH - 1.
+                        // Total depth is MAX_LAYOUT_ELEMENT_DEPTH (if we count the head).
+                        layout(box(children)), RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+        assertThat(mRootContainer.getChildCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void layoutDepthForLayoutWithSpanner() throws Exception {
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+
+        assertThrows(
+                ExecutionException.class,
+                () ->
+                        renderAndAttachLayout(
+                                // Total number of views is = MAX_LAYOUT_ELEMENT_DEPTH  + 1 (span
+                                // text)
+                                layout(
+                                        recursiveBox(
+                                                MAX_LAYOUT_ELEMENT_DEPTH,
+                                                spannable(spanText("Hello"))))));
+
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        // Total number of views is = (MAX_LAYOUT_ELEMENT_DEPTH -1)  + 1 (span text)
+                        layout(
+                                recursiveBox(
+                                        MAX_LAYOUT_ELEMENT_DEPTH - 1,
+                                        spannable(spanText("Hello")))),
+                        RESOURCES,
+                        mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+        assertThat(mRootContainer.getChildCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void layoutDepthForLayoutWithArcAdapter() throws Exception {
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
+        assertThrows(
+                ExecutionException.class,
+                () ->
+                        renderAndAttachLayout(
+                                // Total number of views is = 1 (Arc) + (MAX_LAYOUT_ELEMENT_DEPTH)
+                                layout(arc(arcAdapter(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH))))));
+
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        // Total number of views is = 1 (Arc) + (MAX_LAYOUT_ELEMENT_DEPTH - 1)
+                        // = MAX_LAYOUT_ELEMENT_DEPTH
+                        layout(arc(arcAdapter(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH - 1)))),
+                        RESOURCES,
+                        mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+        assertThat(mRootContainer.getChildCount()).isEqualTo(1);
+    }
+
+    private void renderAndAttachLayout(Layout layout) throws Exception {
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+        assertNoException(result);
+    }
+
+    private static LayoutNode recursiveBox(int depth) {
+        if (depth == 1) {
+            return box();
+        }
+        return box(recursiveBox(depth - 1));
+    }
+
+    private static LayoutNode recursiveBox(int depth, LayoutNode leaf) {
+        if (depth == 1) {
+            return leaf;
+        }
+        return box(recursiveBox(depth - 1, leaf));
+    }
+
     private void setupInstance(boolean adaptiveUpdateRatesEnabled) {
         FakeExecutorService uiThreadExecutor =
                 new FakeExecutorService(new Handler(Looper.getMainLooper()));
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index 3219024..6a30db4 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -2834,10 +2834,16 @@
              * Sets the style of font to use (size, bold etc). If not specified, defaults to the
              * platform's default body font.
              *
+             * DynamicColor is not supported for SpanText.
+             *
              * @since 1.0
              */
             @NonNull
             public Builder setFontStyle(@NonNull FontStyle fontStyle) {
+                ColorProp colorProp = fontStyle.getColor();
+                if (colorProp != null && colorProp.getDynamicValue() != null) {
+                    throw new IllegalArgumentException("SpanText does not support DynamicColor.");
+                }
                 mImpl.setFontStyle(fontStyle.toProto());
                 mFingerprint.recordPropertyUpdate(
                         2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
diff --git a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
index dd4398b..1790ab2 100644
--- a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
+++ b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
@@ -183,11 +183,11 @@
  *
  * Manifest requirements:
  * - The manifest declaration of this service must include an intent filter for
- *   android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST.
+ *   `android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST`.
  * - A ComplicationDataSourceService must include a `meta-data` tag with
- *   android.support.wearable.complications.SUPPORTED_TYPES in its manifest entry.
+ *   `android.support.wearable.complications.SUPPORTED_TYPES` in its manifest entry.
  *
- * The value of android.support.wearable.complications.SUPPORTED_TYPES should be a comma separated
+ * The value of `android.support.wearable.complications.SUPPORTED_TYPES` should be a comma separated
  * list of types supported by the data source, from this table:
  *
  * | Androidx class                       | Tag name          |
@@ -205,47 +205,49 @@
  * multiple types in a single complication slot, the watch face will determine which types it
  * prefers.
  *
- * For example, a complication data source that supports the RANGED_VALUE, SHORT_TEXT, and ICON
- * types would include the following in its manifest entry:
+ * For example, a complication data source that supports the `RANGED_VALUE`, `SHORT_TEXT`, and
+ * `ICON` types would include the following in its manifest entry:
  * ```
- * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES"
- * android:value="RANGED_VALUE,SHORT_TEXT,ICON"/>
+ * <meta-data
+ *     android:name="android.support.wearable.complications.SUPPORTED_TYPES"
+ *     android:value="RANGED_VALUE,SHORT_TEXT,ICON" />
  * ```
  *
- * From android T onwards, it is recommended for Complication DataSourceServices to be direct boot
+ * From android T onwards, it is recommended for [ComplicationDataSourceService]s to be direct boot
  * aware because the system is able to fetch complications before the lock screen has been removed.
- * To do this add android:directBootAware="true" to your service tag.
+ * To do this add `android:directBootAware="true"` to your service tag.
  * - A provider can choose to trust one or more watch faces by including the following in its
  *   manifest entry:
  * ```
- * <meta-data android:name="android.support.wearable.complications.SAFE_WATCH_FACES
- * android:value="com.pkg1/com.trusted.wf1,com.pkg2/com.trusted.wf2"/>
+ * <meta-data
+ *     android:name="android.support.wearable.complications.SAFE_WATCH_FACES"
+ *     android:value="com.pkg1/com.trusted.wf1,com.pkg2/com.trusted.wf2" />
  * ```
  *
  * The listed watch faces will not need
- * 'com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA' in order to receive
+ * `com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA` in order to receive
  * complications from this provider. Also the provider may choose to serve different types to safe
  * watch faces by including the following in its manifest:
  * ```
- * <meta-data android:name=
- *     "androidx.wear.watchface.complications.datasource.SAFE_WATCH_FACE_SUPPORTED_TYPES"
- *      android:value="ICON"/>
+ * <meta-data
+ *     android:name="androidx.wear.watchface.complications.datasource.SAFE_WATCH_FACE_SUPPORTED_TYPES"
+ *     android:value="ICON" />
  * ```
  *
  * In addition the provider can learn if a request is for a safe watchface by examining
- * [ComplicationRequest.isForSafeWatchFace]. Note SAFE_WATCH_FACE_SUPPORTED_TYPES and
- * isForSafeWatchFace are gated behind the privileged permission
+ * [ComplicationRequest.isForSafeWatchFace]. Note `SAFE_WATCH_FACE_SUPPORTED_TYPES` and
+ * `isForSafeWatchFace` are gated behind the privileged permission
  * `com.google.wear.permission.GET_IS_FOR_SAFE_WATCH_FACE`.
  * - A ComplicationDataSourceService should include a `meta-data` tag with
- *   `android.support.wearable.complications.UPDATE_PERIOD_SECONDS` its manifest entry. The value of
- *   this tag is the number of seconds the complication data source would like to elapse between
+ *   `android.support.wearable.complications.UPDATE_PERIOD_SECONDS` in its manifest entry. The value
+ *   of this tag is the number of seconds the complication data source would like to elapse between
  *   update requests.
  *
  * **Note that update requests are not guaranteed to be sent with this frequency.** For
- * complications with frequent updates they can also register a separate
- * [METADATA_KEY_IMMEDIATE_UPDATE_PERIOD_MILLISECONDS] meta data tag which supports sampling at up
- * to 1Hz when the watch face is visible and non-ambient, however this also requires the
- * DataSourceService to have the privileged permission
+ * complications with frequent updates they can also register a separate `meta-data` tag with
+ * `androidx.wear.watchface.complications.data.source.IMMEDIATE_UPDATE_PERIOD_MILLISECONDS` in their
+ * manifest which supports sampling at up to 1Hz when the watch face is visible and non-ambient,
+ * however this also requires the application to have the privileged permission
  * `com.google.android.wearable.permission.USE_IMMEDIATE_COMPLICATION_UPDATE`.
  *
  * If a complication data source never needs to receive update requests beyond the one sent when a
@@ -254,37 +256,39 @@
  * For example, a complication data source that would like to update at most every hour should
  * include the following in its manifest entry:
  * ```
- * <meta-data android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
- * android:value="3600"/>
+ * <meta-data
+ *     android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
+ *     android:value="3600" />
  * ```
- * - A ComplicationDataSourceService can include a `meta-data` tag with
+ * - A [ComplicationDataSourceService] can include a `meta-data` tag with
  *   android.support.wearable.complications.PROVIDER_CONFIG_ACTION its manifest entry to cause a
  *   configuration activity to be shown when the complication data source is selected.
  *
  * The configuration activity must reside in the same package as the complication data source, and
  * must register an intent filter for the action specified here, including
- * android.support.wearable.complications.category.PROVIDER_CONFIG as well as
+ * `android.support.wearable.complications.category.PROVIDER_CONFIG` as well as
  * [Intent.CATEGORY_DEFAULT] as categories.
  *
  * The complication id being configured will be included in the intent that starts the config
- * activity using the extra key android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_ID.
+ * activity using the extra key
+ * `android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_ID`.
  *
  * The complication type that will be requested from the complication data source will also be
- * included, using the extra key android.support.wearable.complications
- * .EXTRA_CONFIG_COMPLICATION_TYPE.
+ * included, using the extra key
+ * `android.support.wearable.complications.EXTRA_CONFIG_COMPLICATION_TYPE`.
  *
  * The complication data source's [ComponentName] will also be included in the intent that starts
  * the config activity, using the extra key
- * android.support.wearable.complications.EXTRA_CONFIG_PROVIDER_COMPONENT.
+ * `android.support.wearable.complications.EXTRA_CONFIG_PROVIDER_COMPONENT`.
  *
  * The config activity must call [Activity.setResult] with either [Activity.RESULT_OK] or
  * [Activity.RESULT_CANCELED] before it is finished, to tell the system whether or not the
  * complication data source should be set on the given complication.
  *
- * It is possible to provide additional 'meta-data' tag
- * androidx.watchface.complications.datasource.DEFAULT_CONFIG_SUPPORTED in the service set to "true"
- * to let the system know that the data source is able to provide complication data before it is
- * configured.
+ * It is possible to provide additional `meta-data` tag
+ * `androidx.watchface.complications.datasource.DEFAULT_CONFIG_SUPPORTED` in the service set to
+ * `"true"` to let the system know that the data source is able to provide complication data before
+ * it is configured.
  * - The manifest entry for the service should also include an android:icon attribute. The icon
  *   provided there should be a single-color white icon that represents the complication data
  *   source. This icon will be shown in the complication data source chooser interface, and may also
@@ -298,8 +302,8 @@
  * limit of 100 data sources per APK. Above that the companion watchface editor won't support this
  * complication data source app.
  *
- * There's no need to call setDataSource for any the ComplicationData Builders because the system
- * will append this value on your behalf.
+ * There's no need to call `setDataSource` for any the [ComplicationData] Builders because the
+ * system will append this value on your behalf.
  */
 public abstract class ComplicationDataSourceService : Service() {
     private var wrapper: IComplicationProviderWrapper? = null
@@ -768,15 +772,18 @@
          * the [ComplicationData], but omitting the "TYPE_" prefix, e.g. `SHORT_TEXT`, `LONG_TEXT`,
          * `RANGED_VALUE`.
          *
-         * The order in which types are listed has no significance. In the case where a watch face
-         * supports multiple types in a single complication slot, the watch face will determine
-         * which types it prefers.
+         * The order of types in `METADATA_KEY_SUPPORTED_TYPES` has no significance. During
+         * complication data source selection, each item in the complication slot's supported types
+         * is checked against entries in the data source's `METADATA_KEY_SUPPORTED_TYPES` and the
+         * first matching entry from the slot's support types (if any) is chosen. If there are no
+         * matches then this data source is not eligible to be selected in that slot.
          *
          * For example, a complication data source that supports the RANGED_VALUE, SHORT_TEXT, and
          * ICON type would include the following in its manifest entry:
          * ```
-         * <meta-data android:name="android.support.wearable.complications.SUPPORTED_TYPES"
-         * android:value="RANGED_VALUE,SHORT_TEXT,ICON"/>
+         * <meta-data
+         *     android:name="android.support.wearable.complications.SUPPORTED_TYPES"
+         *     android:value="RANGED_VALUE,SHORT_TEXT,ICON" />
          * ```
          */
         // TODO(b/192233205): Migrate value to androidx.
@@ -812,8 +819,9 @@
          * For example, a complication data source that would like to update every ten minutes
          * should include the following in its manifest entry:
          * ```
-         * <meta-data android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
-         * android:value="600"/>
+         * <meta-data
+         *     android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
+         *     android:value="600" />
          * ```
          */
         // TODO(b/192233205): Migrate value to androidx.
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
index 5f14a6f..c36b80e 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
@@ -345,8 +345,11 @@
  * @param accessibilityTraversalIndex Used to sort Complications when generating accessibility
  *   content description labels.
  * @param bounds The complication slot's [ComplicationSlotBounds].
- * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot. Used
- *   during complication data source selection, this list should be non-empty.
+ * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot, must be
+ *   non-empty. During complication data source selection, each item in this list is compared in
+ *   turn with entries from a data source's data source's supported types. The first matching entry
+ *   from `supportedTypes` is chosen. If there are no matches then that data source is not eligible
+ *   to be selected in this slot.
  * @param defaultPolicy The [DefaultComplicationDataSourcePolicy] which controls the initial
  *   complication data source when the watch face is first installed.
  * @param defaultDataSourceType The default [ComplicationType] for the default complication data
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index aff5089..c32fa26 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -1320,7 +1320,12 @@
         internal var immutableSystemStateDone = false
         internal var immutableChinHeightDone = false
         internal var systemHasSentWatchUiState = false
-        internal var resourceOnlyWatchFacePackageName: String? = headlessComponentName?.packageName
+        internal var resourceOnlyWatchFacePackageName: String? =
+            if (this@WatchFaceService is WatchFaceRuntimeService) {
+                headlessComponentName?.packageName
+            } else {
+                null
+            }
 
         private var asyncWatchFaceConstructionPending = false
 
@@ -2103,7 +2108,10 @@
                 setWatchUiState(params.watchUiState, fromSysUi = false)
                 initialUserStyle = params.userStyle
 
-                resourceOnlyWatchFacePackageName = params.auxiliaryComponentPackageName
+                // For a resource only watch face, the auxiliaryComponentPackageName will be null.
+                if (this@WatchFaceService is WatchFaceRuntimeService) {
+                    resourceOnlyWatchFacePackageName = params.auxiliaryComponentPackageName
+                }
 
                 mutableWatchState.watchFaceInstanceId.value = sanitizeWatchFaceId(params.instanceId)
                 val watchState = mutableWatchState.asWatchState()
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index d55140d..3a6c4ec 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -88,6 +88,7 @@
 import androidx.wear.watchface.data.WatchUiState
 import androidx.wear.watchface.style.CurrentUserStyleRepository
 import androidx.wear.watchface.style.UserStyle
+import androidx.wear.watchface.style.UserStyleFlavors
 import androidx.wear.watchface.style.UserStyleSchema
 import androidx.wear.watchface.style.UserStyleSetting
 import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting
@@ -6549,11 +6550,12 @@
     @Test
     public fun createHeadlessSessionDelegate_onDestroy() {
         val context = ApplicationProvider.getApplicationContext<Context>()
-        val componentName = ComponentName(context, TestNopCanvasWatchFaceService::class.java)
+        val componentName =
+            ComponentName(context, TestNopCanvasWatchFaceServiceWithHandler::class.java)
         lateinit var delegate: WatchFace.EditorDelegate
 
         // Allows us to programmatically control tasks.
-        TestNopCanvasWatchFaceService.handler = this.handler
+        TestNopCanvasWatchFaceServiceWithHandler.handler = this.handler
 
         CoroutineScope(handler.asCoroutineDispatcher().immediate).launch {
             delegate =
@@ -6609,7 +6611,7 @@
         )
 
         // Allows us to programmatically control tasks.
-        TestNopCanvasWatchFaceService.handler = this.handler
+        TestNopCanvasWatchFaceServiceWithHandler.handler = this.handler
 
         CoroutineScope(handler.asCoroutineDispatcher().immediate).launch {
             delegate =
@@ -6709,6 +6711,115 @@
             .isEqualTo(INTERACTIVE_INSTANCE_ID)
     }
 
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.R)
+    public fun interactive_wf_has_null_resourceOnlyWatchFacePackageName() {
+        initWallpaperInteractiveWatchFaceInstance(
+            WatchFaceType.ANALOG,
+            emptyList(),
+            UserStyleSchema(emptyList()),
+            WallpaperInteractiveWatchFaceInstanceParams(
+                INTERACTIVE_INSTANCE_ID,
+                DeviceConfig(false, false, 0, 0),
+                WatchUiState(false, 0),
+                UserStyle(emptyMap()).toWireFormat(),
+                null,
+                null,
+                null
+            )
+        )
+
+        assertThat(engineWrapper.resourceOnlyWatchFacePackageName).isNull()
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.O_MR1)
+    public fun headless_wf_has_null_resourceOnlyWatchFacePackageName() {
+        val service = TestNopCanvasWatchFaceService(context)
+        val componentName = ComponentName("test.watchface.app", "test.watchface.class")
+
+        engineWrapper =
+            service.createHeadlessEngine(componentName) as WatchFaceService.EngineWrapper
+        engineWrapper.createHeadlessInstance(
+            HeadlessWatchFaceInstanceParams(
+                componentName,
+                DeviceConfig(false, false, 100, 200),
+                100,
+                100,
+                null
+            )
+        )
+
+        assertThat(engineWrapper.resourceOnlyWatchFacePackageName).isNull()
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.R)
+    public fun interactive_wf_runtime_has_non_null_resourceOnlyWatchFacePackageName() {
+        val service = TestNopWatchFaceRuntimeService(context)
+        InteractiveInstanceManager
+            .getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance(
+                InteractiveInstanceManager.PendingWallpaperInteractiveWatchFaceInstance(
+                    WallpaperInteractiveWatchFaceInstanceParams(
+                        SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX + "Interactive",
+                        DeviceConfig(false, false, 0, 0),
+                        WatchUiState(false, 0),
+                        UserStyle(emptyMap()).toWireFormat(),
+                        emptyList(),
+                        "com.resource.only.package",
+                        null
+                    ),
+                    object : IPendingInteractiveWatchFace.Stub() {
+                        override fun getApiVersion() = IPendingInteractiveWatchFace.API_VERSION
+
+                        override fun onInteractiveWatchFaceCreated(
+                            iInteractiveWatchFace: IInteractiveWatchFace
+                        ) {
+                            interactiveWatchFaceInstance = iInteractiveWatchFace
+                        }
+
+                        override fun onInteractiveWatchFaceCrashed(exception: CrashInfoParcel?) {
+                            fail("WatchFace crashed: $exception")
+                        }
+                    }
+                )
+            )
+
+        engineWrapper = service.onCreateEngine() as WatchFaceService.EngineWrapper
+        engineWrapper.onCreate(surfaceHolder)
+        engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100)
+
+        assertThat(engineWrapper.resourceOnlyWatchFacePackageName)
+            .isEqualTo("com.resource.only.package")
+        engineWrapper.onDestroy()
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.O_MR1)
+    public fun headless_wf_runtime_has_non_null_resourceOnlyWatchFacePackageName() {
+        val service = TestNopWatchFaceRuntimeService(context)
+        val componentName = ComponentName("com.resource.only.package", "null")
+
+        engineWrapper =
+            service.createHeadlessEngine(componentName) as WatchFaceService.EngineWrapper
+        engineWrapper.createHeadlessInstance(
+            HeadlessWatchFaceInstanceParams(
+                componentName,
+                DeviceConfig(false, false, 100, 200),
+                100,
+                100,
+                null
+            )
+        )
+
+        assertThat(engineWrapper.resourceOnlyWatchFacePackageName)
+            .isEqualTo("com.resource.only.package")
+    }
+
     private fun getLeftShortTextComplicationDataText(): CharSequence {
         val complication =
             complicationSlotsManager[LEFT_COMPLICATION_ID]!!.complicationData.value
@@ -6743,7 +6854,115 @@
     private suspend fun <T> Deferred<T>.awaitWithTimeout(): T = withTimeout(1000) { await() }
 }
 
-class TestNopCanvasWatchFaceService : WatchFaceService() {
+class TestNopWatchFaceRuntimeService(testContext: Context) : WatchFaceRuntimeService() {
+    var lastResourceOnlyWatchFacePackageName: String? = null
+
+    init {
+        attachBaseContext(testContext)
+    }
+
+    override fun createUserStyleSchema(resourceOnlyWatchFacePackageName: String) =
+        UserStyleSchema(emptyList())
+
+    override fun createComplicationSlotsManager(
+        currentUserStyleRepository: CurrentUserStyleRepository,
+        resourceOnlyWatchFacePackageName: String
+    ) = ComplicationSlotsManager(emptyList(), currentUserStyleRepository)
+
+    override fun createUserStyleFlavors(
+        currentUserStyleRepository: CurrentUserStyleRepository,
+        complicationSlotsManager: ComplicationSlotsManager,
+        resourceOnlyWatchFacePackageName: String
+    ) = UserStyleFlavors()
+
+    override suspend fun createWatchFace(
+        surfaceHolder: SurfaceHolder,
+        watchState: WatchState,
+        complicationSlotsManager: ComplicationSlotsManager,
+        currentUserStyleRepository: CurrentUserStyleRepository,
+        resourceOnlyWatchFacePackageName: String
+    ): WatchFace {
+        lastResourceOnlyWatchFacePackageName = resourceOnlyWatchFacePackageName
+        System.out.println("<<< createWatchFace " + resourceOnlyWatchFacePackageName)
+        return WatchFace(
+            WatchFaceType.DIGITAL,
+            @Suppress("deprecation")
+            object :
+                Renderer.CanvasRenderer(
+                    surfaceHolder,
+                    currentUserStyleRepository,
+                    watchState,
+                    CanvasType.HARDWARE,
+                    16
+                ) {
+                override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+                    // Intentionally empty.
+                }
+
+                override fun renderHighlightLayer(
+                    canvas: Canvas,
+                    bounds: Rect,
+                    zonedDateTime: ZonedDateTime
+                ) {
+                    // Intentionally empty.
+                }
+            }
+        )
+    }
+
+    override fun getSystemTimeProvider() =
+        object : SystemTimeProvider {
+            override fun getSystemTimeMillis() = 123456789L
+
+            override fun getSystemTimeZoneId() = ZoneId.of("UTC")
+        }
+}
+
+class TestNopCanvasWatchFaceService(testContext: Context) : WatchFaceService() {
+    init {
+        attachBaseContext(testContext)
+    }
+
+    override suspend fun createWatchFace(
+        surfaceHolder: SurfaceHolder,
+        watchState: WatchState,
+        complicationSlotsManager: ComplicationSlotsManager,
+        currentUserStyleRepository: CurrentUserStyleRepository
+    ) =
+        WatchFace(
+            WatchFaceType.DIGITAL,
+            @Suppress("deprecation")
+            object :
+                Renderer.CanvasRenderer(
+                    surfaceHolder,
+                    currentUserStyleRepository,
+                    watchState,
+                    CanvasType.HARDWARE,
+                    16
+                ) {
+                override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime) {
+                    // Intentionally empty.
+                }
+
+                override fun renderHighlightLayer(
+                    canvas: Canvas,
+                    bounds: Rect,
+                    zonedDateTime: ZonedDateTime
+                ) {
+                    // Intentionally empty.
+                }
+            }
+        )
+
+    override fun getSystemTimeProvider() =
+        object : SystemTimeProvider {
+            override fun getSystemTimeMillis() = 123456789L
+
+            override fun getSystemTimeZoneId() = ZoneId.of("UTC")
+        }
+}
+
+class TestNopCanvasWatchFaceServiceWithHandler : WatchFaceService() {
     companion object {
         lateinit var handler: Handler
     }
@@ -6796,6 +7015,6 @@
 @RequiresApi(27)
 class TestWatchFaceControlService : WatchFaceControlService() {
     override fun createWatchFaceService(watchFaceName: ComponentName): WatchFaceService? {
-        return TestNopCanvasWatchFaceService()
+        return TestNopCanvasWatchFaceServiceWithHandler()
     }
 }