Merge "Export Ink module to Jetpack" into androidx-main
diff --git a/appfunctions/appfunctions-compiler/build.gradle b/appfunctions/appfunctions-compiler/build.gradle
index c327d24..1096124 100644
--- a/appfunctions/appfunctions-compiler/build.gradle
+++ b/appfunctions/appfunctions-compiler/build.gradle
@@ -37,6 +37,9 @@
     api(libs.kotlinStdlib)
     implementation(libs.kspApi)
     implementation(libs.kotlinPoet)
+    implementation(libs.kotlinReflect, {
+        exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
+    })
 
     // Added for reusing AppFunctionMetadata classes.
     implementationAarAsJar(project(":appfunctions:appfunctions-common"))
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
index c8feabb..58379fd 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
@@ -19,6 +19,7 @@
 import androidx.appfunctions.compiler.core.ProcessingException
 import androidx.appfunctions.compiler.core.logException
 import androidx.appfunctions.compiler.processors.AppFunctionIdProcessor
+import androidx.appfunctions.compiler.processors.AppFunctionIndexXmlProcessor
 import androidx.appfunctions.compiler.processors.AppFunctionInventoryProcessor
 import androidx.appfunctions.compiler.processors.AppFunctionInvokerProcessor
 import androidx.appfunctions.compiler.processors.AppFunctionLegacyIndexXmlProcessor
@@ -56,11 +57,20 @@
             val idProcessor = AppFunctionIdProcessor(environment.codeGenerator)
             val inventoryProcessor = AppFunctionInventoryProcessor(environment.codeGenerator)
             val invokerProcessor = AppFunctionInvokerProcessor(environment.codeGenerator)
+            // We generate both XML formats supported by old and new AppSearch indexer respectively
+            // as it can't be guaranteed that the device will have the latest version of AppSearch.
             // TODO: Add compiler option to disable legacy xml generator.
             val legacyIndexXmlProcessor =
                 AppFunctionLegacyIndexXmlProcessor(environment.codeGenerator)
+            val indexXmlProcessor = AppFunctionIndexXmlProcessor(environment.codeGenerator)
             return AppFunctionCompiler(
-                listOf(idProcessor, inventoryProcessor, invokerProcessor, legacyIndexXmlProcessor),
+                listOf(
+                    idProcessor,
+                    inventoryProcessor,
+                    invokerProcessor,
+                    legacyIndexXmlProcessor,
+                    indexXmlProcessor
+                ),
                 environment.logger,
             )
         }
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionIndexXmlProcessor.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionIndexXmlProcessor.kt
new file mode 100644
index 0000000..dfc5462
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionIndexXmlProcessor.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2025 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.appfunctions.compiler.processors
+
+import androidx.appfunctions.compiler.core.AnnotatedAppFunctions
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver
+import androidx.appfunctions.metadata.AppFunctionMetadata
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.symbol.KSAnnotated
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.OutputKeys
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+import kotlin.reflect.KProperty1
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Generates AppFunction's index xml file with all properties of [AppFunctionMetadata] for the
+ * AppSearch indexer to index.
+ *
+ * The generator would write an XML file as `/assets/app_functions_dynamic_schema.xml`. The file
+ * would be packaged into the APK's asset when assembled. So that the AppSearch indexer can look up
+ * the asset and inject metadata into platform AppSearch database accordingly.
+ */
+class AppFunctionIndexXmlProcessor(
+    private val codeGenerator: CodeGenerator,
+) : SymbolProcessor {
+
+    override fun process(resolver: Resolver): List<KSAnnotated> {
+        generateIndexXml(AppFunctionSymbolResolver(resolver).resolveAnnotatedAppFunctions())
+        return emptyList()
+    }
+
+    /**
+     * Generates AppFunction's index xml files for indexer in App Search.
+     *
+     * @param appFunctionsByClass a collection of functions annotated with @AppFunction grouped by
+     *   their enclosing classes.
+     */
+    private fun generateIndexXml(
+        appFunctionsByClass: List<AnnotatedAppFunctions>,
+    ) {
+        if (appFunctionsByClass.isEmpty()) {
+            return
+        }
+        writeXmlFile(appFunctionsByClass)
+    }
+
+    private fun writeXmlFile(
+        appFunctionsByClass: List<AnnotatedAppFunctions>,
+    ) {
+        val appFunctionMetadataList =
+            appFunctionsByClass.flatMap { it.createAppFunctionMetadataInstances() }
+
+        val xmlDocumentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+        val xmlDocument = xmlDocumentBuilder.newDocument().apply { xmlStandalone = true }
+
+        val appFunctionsElement = xmlDocument.createElement(APP_FUNCTIONS_ELEMENTS_TAG)
+        xmlDocument.appendChild(appFunctionsElement)
+
+        for (appFunctionMetadata in appFunctionMetadataList) {
+            val appFunctionElement =
+                xmlDocument.createElementWithInstance(
+                    APP_FUNCTION_ITEM_TAG,
+                    appFunctionMetadata,
+                    // Below properties are named differently in platform's
+                    // AppFunctionStaticMetadata GD hence we encode them in XML accordingly.
+                    customTagNames = mapOf("isEnabledByDefault" to "enabledByDefault"),
+                    // Irrelevant properties that do not need to be encoded in XML.
+                    skipProperties = setOf("namespace")
+                )
+            appFunctionElement.appendChild(
+                xmlDocument.createElementWithTextNode(APP_FUNCTION_ID_TAG, appFunctionMetadata.id)
+            )
+            appFunctionsElement.appendChild(appFunctionElement)
+        }
+
+        val transformer =
+            TransformerFactory.newInstance().newTransformer().apply {
+                setOutputProperty(OutputKeys.INDENT, "yes")
+                setOutputProperty(OutputKeys.ENCODING, "UTF-8")
+                setOutputProperty(OutputKeys.VERSION, "1.0")
+                setOutputProperty(OutputKeys.STANDALONE, "yes")
+            }
+
+        codeGenerator
+            .createNewFile(
+                Dependencies(
+                    aggregating = true,
+                    *appFunctionsByClass.mapNotNull { it.getSourceFile() }.toTypedArray()
+                ),
+                XML_PACKAGE_NAME,
+                XML_FILE_NAME,
+                XML_EXTENSION
+            )
+            .use { stream -> transformer.transform(DOMSource(xmlDocument), StreamResult(stream)) }
+    }
+
+    /**
+     * Creates an XML element from [instance], including nested structures and collections.
+     *
+     * This function recursively converts a data class instance into an XML element, handling nested
+     * data classes and collections appropriately. For non-data-class values, it creates text nodes.
+     *
+     * @param elementName The name of the root XML element to create.
+     * @param instance The instance to convert into an XML structure.
+     * @param customTagNames Mapping of property names to customized tag names when creating nodes.
+     * @param skipProperties Property names to skip when creating XML elements.
+     * @return The created XML element representing the instance.
+     */
+    private fun Document.createElementWithInstance(
+        elementName: String,
+        instance: Any,
+        customTagNames: Map<String, String>,
+        skipProperties: Set<String>,
+    ): Element {
+        if (instance.isPrimitiveType()) {
+            return createElementWithTextNode(elementName, instance.toString())
+        }
+
+        val doc = this
+        val element = createElement(elementName)
+
+        for (property in instance::class.members.filterIsInstance<KProperty1<Any, *>>()) {
+
+            if (property.name in skipProperties) continue
+
+            val value = property.get(instance) ?: continue
+            val propertyName = customTagNames[property.name] ?: property.name
+
+            when {
+                value is List<*> ->
+                    value
+                        .filterNotNull()
+                        .map { item ->
+                            doc.createElementWithInstance(
+                                propertyName,
+                                item,
+                                customTagNames,
+                                skipProperties
+                            )
+                        }
+                        .forEach(element::appendChild)
+                else ->
+                    element.appendChild(
+                        doc.createElementWithInstance(
+                            propertyName,
+                            value,
+                            customTagNames,
+                            skipProperties
+                        )
+                    )
+            }
+        }
+
+        return element
+    }
+
+    private fun Any.isPrimitiveType(): Boolean {
+        return this is Byte ||
+            this is Short ||
+            this is Int ||
+            this is Long ||
+            this is Float ||
+            this is Double ||
+            this is Char ||
+            this is Boolean ||
+            this is String
+    }
+
+    private fun Document.createElementWithTextNode(elementName: String, text: String): Element =
+        createElement(elementName).apply { appendChild(createTextNode(text)) }
+
+    private companion object {
+        const val XML_PACKAGE_NAME = "assets"
+        const val XML_FILE_NAME = "app_functions_dynamic_schema"
+        const val XML_EXTENSION = "xml"
+        const val APP_FUNCTIONS_ELEMENTS_TAG = "appfunctions"
+        const val APP_FUNCTION_ITEM_TAG = "appfunction"
+        const val APP_FUNCTION_ID_TAG = "functionId"
+    }
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
index 15b9bec..76dc138 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
@@ -206,13 +206,72 @@
     }
 
     @Test
-    fun testSimpleFunction_noOverride_hasCompileError() {
-        val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
+    fun testFakeNoArg_freeForm_genLegacyIndexXmlFile_success() {
+        val report =
+            compilationTestHelper.compileAll(
+                sourceFileNames = listOf("FakeNoArg_FreeForm_Function.KT")
+            )
 
         compilationTestHelper.assertSuccessWithResourceContent(
             report = report,
             expectGeneratedResourceFileName = "app_functions.xml",
-            goldenFileName = "simpleFunction_noSchema_app_function.xml"
+            goldenFileName = "fakeNoArg_freeForm_function_app_function.xml"
+        )
+    }
+
+    @Test
+    fun testFakeNoArgImpl_genIndexXmlFile_success() {
+        val report =
+            compilationTestHelper.compileAll(
+                sourceFileNames = listOf("FakeNoArgImpl.KT", "FakeSchemas.KT")
+            )
+
+        compilationTestHelper.assertSuccessWithResourceContent(
+            report = report,
+            expectGeneratedResourceFileName = "app_functions_dynamic_schema.xml",
+            goldenFileName = "fakeNoArgImpl_app_function_dynamic_schema.xml"
+        )
+    }
+
+    @Test
+    fun testFakeNoArgImp_isEnabledTrue_genIndexXmlFile_success() {
+        val report =
+            compilationTestHelper.compileAll(
+                sourceFileNames = listOf("FakeNoArgImpl_IsEnabled_True.KT", "FakeSchemas.KT")
+            )
+
+        compilationTestHelper.assertSuccessWithResourceContent(
+            report = report,
+            expectGeneratedResourceFileName = "app_functions_dynamic_schema.xml",
+            goldenFileName = "fakeNoArgImpl_isEnabled_true_app_function_dynamic_schema.xml"
+        )
+    }
+
+    @Test
+    fun testFakeNoArgImp_isEnabledFalse_genIndexXmlFile_success() {
+        val report =
+            compilationTestHelper.compileAll(
+                sourceFileNames = listOf("FakeNoArgImpl_IsEnabled_False.KT", "FakeSchemas.KT")
+            )
+
+        compilationTestHelper.assertSuccessWithResourceContent(
+            report = report,
+            expectGeneratedResourceFileName = "app_functions_dynamic_schema.xml",
+            goldenFileName = "fakeNoArgImpl_isEnabled_false_app_function_dynamic_schema.xml"
+        )
+    }
+
+    @Test
+    fun testFakeNoArg_freeForm_genIndexXmlFile_success() {
+        val report =
+            compilationTestHelper.compileAll(
+                sourceFileNames = listOf("FakeNoArg_FreeForm_Function.KT")
+            )
+
+        compilationTestHelper.assertSuccessWithResourceContent(
+            report = report,
+            expectGeneratedResourceFileName = "app_functions.xml",
+            goldenFileName = "fakeNoArg_freeForm_function_app_function_dynamic_schema.xml"
         )
     }
 }
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
index 82366ca..bdc6387 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
@@ -129,7 +129,8 @@
               the content of golden file [${goldenFile.path}].
 
               To update the golden file,
-              run `cp $generatedFilePath ${goldenFilePath}`
+              run:
+               cp $generatedFilePath ${goldenFilePath}
             """
                     .trimIndent()
             )
@@ -169,7 +170,8 @@
               the content of golden file [${goldenFile.path}].
 
               To update the golden file,
-              run `cp $generatedFilePath ${goldenFilePath}`
+              run:
+               cp $generatedFilePath ${goldenFilePath}
             """
                     .trimIndent()
             )
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeNoArg_FreeForm_Function.KT b/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeNoArg_FreeForm_Function.KT
new file mode 100644
index 0000000..54160f4e
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeNoArg_FreeForm_Function.KT
@@ -0,0 +1,8 @@
+package com.testdata
+
+import androidx.appfunctions.AppFunction
+import androidx.appfunctions.AppFunctionContext
+
+class FakeNoArg {
+    @AppFunction fun noArg(appFunctionContext: AppFunctionContext) {}
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_app_function_dynamic_schema.xml b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_app_function_dynamic_schema.xml
new file mode 100644
index 0000000..9571a33
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_app_function_dynamic_schema.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<appfunctions>
+    <appfunction>
+        <components>
+            <id>unused</id>
+        </components>
+        <displayNameRes>0</displayNameRes>
+        <id>com.testdata.FakeNoArgImpl#noArg</id>
+        <enabledByDefault>true</enabledByDefault>
+        <isRestrictToTrustedCaller>false</isRestrictToTrustedCaller>
+        <response>
+            <id>unused</id>
+            <isNullable>false</isNullable>
+        </response>
+        <schema>
+            <id>unused</id>
+            <schemaCategory>fake_schema_category</schemaCategory>
+            <schemaName>noArg</schemaName>
+            <schemaVersion>1</schemaVersion>
+        </schema>
+        <functionId>com.testdata.FakeNoArgImpl#noArg</functionId>
+    </appfunction>
+</appfunctions>
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_isEnabled_false_app_function_dynamic_schema.xml b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_isEnabled_false_app_function_dynamic_schema.xml
new file mode 100644
index 0000000..ca81c6a
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_isEnabled_false_app_function_dynamic_schema.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<appfunctions>
+    <appfunction>
+        <components>
+            <id>unused</id>
+        </components>
+        <displayNameRes>0</displayNameRes>
+        <id>com.testdata.FakeNoArgImpl_IsEnabled_False#noArg</id>
+        <enabledByDefault>false</enabledByDefault>
+        <isRestrictToTrustedCaller>false</isRestrictToTrustedCaller>
+        <response>
+            <id>unused</id>
+            <isNullable>false</isNullable>
+        </response>
+        <schema>
+            <id>unused</id>
+            <schemaCategory>fake_schema_category</schemaCategory>
+            <schemaName>noArg</schemaName>
+            <schemaVersion>1</schemaVersion>
+        </schema>
+        <functionId>com.testdata.FakeNoArgImpl_IsEnabled_False#noArg</functionId>
+    </appfunction>
+</appfunctions>
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_isEnabled_true_app_function_dynamic_schema.xml b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_isEnabled_true_app_function_dynamic_schema.xml
new file mode 100644
index 0000000..04d6214
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_isEnabled_true_app_function_dynamic_schema.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<appfunctions>
+    <appfunction>
+        <components>
+            <id>unused</id>
+        </components>
+        <displayNameRes>0</displayNameRes>
+        <id>com.testdata.FakeNoArgImpl_IsEnabled_True#noArg</id>
+        <enabledByDefault>true</enabledByDefault>
+        <isRestrictToTrustedCaller>false</isRestrictToTrustedCaller>
+        <response>
+            <id>unused</id>
+            <isNullable>false</isNullable>
+        </response>
+        <schema>
+            <id>unused</id>
+            <schemaCategory>fake_schema_category</schemaCategory>
+            <schemaName>noArg</schemaName>
+            <schemaVersion>1</schemaVersion>
+        </schema>
+        <functionId>com.testdata.FakeNoArgImpl_IsEnabled_True#noArg</functionId>
+    </appfunction>
+</appfunctions>
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/simpleFunction_noSchema_app_function.xml b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArg_freeForm_function_app_function.xml
similarity index 69%
rename from appfunctions/appfunctions-compiler/src/test/test-data/output/simpleFunction_noSchema_app_function.xml
rename to appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArg_freeForm_function_app_function.xml
index 9092801..5f354bb 100644
--- a/appfunctions/appfunctions-compiler/src/test/test-data/output/simpleFunction_noSchema_app_function.xml
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArg_freeForm_function_app_function.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <appfunctions>
     <appfunction>
-        <function_id>com.testdata.SimpleFunction#simpleFunction</function_id>
+        <function_id>com.testdata.FakeNoArg#noArg</function_id>
         <enabled_by_default>true</enabled_by_default>
     </appfunction>
 </appfunctions>
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/simpleFunction_noSchema_app_function.xml b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArg_freeForm_function_app_function_dynamic_schema.xml
similarity index 69%
copy from appfunctions/appfunctions-compiler/src/test/test-data/output/simpleFunction_noSchema_app_function.xml
copy to appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArg_freeForm_function_app_function_dynamic_schema.xml
index 9092801..5f354bb 100644
--- a/appfunctions/appfunctions-compiler/src/test/test-data/output/simpleFunction_noSchema_app_function.xml
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArg_freeForm_function_app_function_dynamic_schema.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <appfunctions>
     <appfunction>
-        <function_id>com.testdata.SimpleFunction#simpleFunction</function_id>
+        <function_id>com.testdata.FakeNoArg#noArg</function_id>
         <enabled_by_default>true</enabled_by_default>
     </appfunction>
 </appfunctions>
diff --git a/compose/material3/OWNERS b/compose/material3/OWNERS
index 3778dd9..16171f7 100644
--- a/compose/material3/OWNERS
+++ b/compose/material3/OWNERS
@@ -4,6 +4,8 @@
 lpf@google.com
 soboleva@google.com
 connieshi@google.com
+kentfan@google.com
+kevinctruong@google.com
 ymarian@google.com
 maxying@google.com
 serniebanders@google.com
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/IconToggleButtonBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/IconToggleButtonBenchmark.kt
index b7d74ba..f99dacb 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/IconToggleButtonBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/IconToggleButtonBenchmark.kt
@@ -23,7 +23,6 @@
 import androidx.compose.material3.FilledTonalIconToggleButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButtonDefaults
-import androidx.compose.material3.IconButtonShapes
 import androidx.compose.material3.IconToggleButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedIconToggleButton
@@ -39,6 +38,7 @@
 import androidx.compose.testutils.benchmark.benchmarkFirstLayout
 import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
 import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
 import androidx.test.filters.LargeTest
 import org.junit.Ignore
 import org.junit.Rule
@@ -88,6 +88,14 @@
     fun iconToggleButton_firstPixel() {
         benchmarkRule.benchmarkToFirstPixel(iconToggleButtonTestCaseFactory)
     }
+
+    @Test
+    fun toggle_recomposeMeasureLayout() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            caseFactory = iconToggleButtonTestCaseFactory,
+            assertOneRecomposition = false
+        )
+    }
 }
 
 internal class IconToggleButtonTestCase(private val type: IconToggleButtonType) :
@@ -125,12 +133,7 @@
                 IconToggleButton(
                     checked = state,
                     onCheckedChange = { /* Do something! */ },
-                    shapes =
-                        IconButtonShapes(
-                            shape = IconButtonDefaults.smallRoundShape,
-                            pressedShape = IconButtonDefaults.smallPressedShape,
-                            checkedShape = IconButtonDefaults.smallSquareShape
-                        )
+                    shapes = IconButtonDefaults.toggleableShapes()
                 ) {
                     Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
                 }
@@ -138,12 +141,7 @@
                 FilledIconToggleButton(
                     checked = state,
                     onCheckedChange = { /* Do something! */ },
-                    shapes =
-                        IconButtonShapes(
-                            shape = IconButtonDefaults.smallRoundShape,
-                            pressedShape = IconButtonDefaults.smallPressedShape,
-                            checkedShape = IconButtonDefaults.smallSquareShape
-                        )
+                    shapes = IconButtonDefaults.toggleableShapes()
                 ) {
                     Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
                 }
@@ -151,12 +149,7 @@
                 FilledTonalIconToggleButton(
                     checked = state,
                     onCheckedChange = { /* Do something! */ },
-                    shapes =
-                        IconButtonShapes(
-                            shape = IconButtonDefaults.smallRoundShape,
-                            pressedShape = IconButtonDefaults.smallPressedShape,
-                            checkedShape = IconButtonDefaults.smallSquareShape
-                        )
+                    shapes = IconButtonDefaults.toggleableShapes()
                 ) {
                     Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
                 }
@@ -164,12 +157,7 @@
                 OutlinedIconToggleButton(
                     checked = state,
                     onCheckedChange = { /* Do something! */ },
-                    shapes =
-                        IconButtonShapes(
-                            shape = IconButtonDefaults.smallRoundShape,
-                            pressedShape = IconButtonDefaults.smallPressedShape,
-                            checkedShape = IconButtonDefaults.smallSquareShape
-                        )
+                    shapes = IconButtonDefaults.toggleableShapes()
                 ) {
                     Icon(Icons.Outlined.Lock, contentDescription = "Localized description")
                 }
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index b4c8ef0..5df4157 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1124,24 +1124,34 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getLargeIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargePressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getMediumIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumPressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getSmallIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallPressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getStandardShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXLargeIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargePressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXSmallIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallPressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
@@ -1165,32 +1175,45 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonVibrantBorder(boolean enabled, boolean checked);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonVibrantColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonVibrantColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonShapes toggleableShapes();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonShapes toggleableShapes(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xSmallContainerSize(optional int widthOption);
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape SmallSelectedSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape filledShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float largeIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largePressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float mediumIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumPressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float smallIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallPressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSelectedRoundShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape standardShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xLargeIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargePressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xSmallIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallPressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSquareShape;
     field public static final androidx.compose.material3.IconButtonDefaults INSTANCE;
   }
@@ -1209,28 +1232,31 @@
   }
 
   public final class IconButtonKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledTonalIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledTonalIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledTonalIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledTonalIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledTonalIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @Deprecated @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @Deprecated @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void OutlinedIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void OutlinedIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void OutlinedIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class IconButtonShapes {
-    ctor public IconButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
-    method public androidx.compose.ui.graphics.Shape getCheckedShape();
+    ctor public IconButtonShapes(androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape pressedShape);
+    method public androidx.compose.material3.IconButtonShapes copy(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape);
     method public androidx.compose.ui.graphics.Shape getPressedShape();
     method public androidx.compose.ui.graphics.Shape getShape();
-    property public final androidx.compose.ui.graphics.Shape checkedShape;
     property public final androidx.compose.ui.graphics.Shape pressedShape;
     property public final androidx.compose.ui.graphics.Shape shape;
   }
@@ -1259,6 +1285,17 @@
     property public final long disabledContentColor;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class IconToggleButtonShapes {
+    ctor public IconToggleButtonShapes(androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.IconToggleButtonShapes copy(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
+    method public androidx.compose.ui.graphics.Shape getCheckedShape();
+    method public androidx.compose.ui.graphics.Shape getPressedShape();
+    method public androidx.compose.ui.graphics.Shape getShape();
+    property public final androidx.compose.ui.graphics.Shape checkedShape;
+    property public final androidx.compose.ui.graphics.Shape pressedShape;
+    property public final androidx.compose.ui.graphics.Shape shape;
+  }
+
   public final class InputChipDefaults {
     method public float getAvatarSize();
     method public float getHeight();
@@ -1739,13 +1776,13 @@
   }
 
   @androidx.compose.runtime.Immutable public final class OutlinedTextFieldDefaults {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
+    method @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void ContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method public androidx.compose.foundation.layout.PaddingValues contentPadding(optional float start, optional float top, optional float end, optional float bottom);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method public float getFocusedBorderThickness();
     method public float getMinHeight();
     method public float getMinWidth();
@@ -2599,14 +2636,14 @@
   }
 
   @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
+    method @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void ContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithLabel(optional float start, optional float end, optional float top, optional float bottom);
     method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithoutLabel(optional float start, optional float top, optional float end, optional float bottom);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
     method @Deprecated public float getFocusedBorderThickness();
     method public float getFocusedIndicatorThickness();
@@ -2616,7 +2653,7 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method @Deprecated public float getUnfocusedBorderThickness();
     method public float getUnfocusedIndicatorThickness();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldColors? colors, optional androidx.compose.ui.graphics.Shape? textFieldShape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
+    method public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldColors? colors, optional androidx.compose.ui.graphics.Shape? textFieldShape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues outlinedTextFieldPadding(optional float start, optional float top, optional float end, optional float bottom);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues textFieldWithLabelPadding(optional float start, optional float end, optional float top, optional float bottom);
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index b4c8ef0..5df4157 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1124,24 +1124,34 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getLargeIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargePressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getMediumIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumPressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getSmallIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallPressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getStandardShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXLargeIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargePressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXSmallIconSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallPressedShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSelectedRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSelectedSquareShape();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
@@ -1165,32 +1175,45 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonVibrantBorder(boolean enabled, boolean checked);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonVibrantColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonVibrantColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonShapes shapes(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonShapes toggleableShapes();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonShapes toggleableShapes(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xSmallContainerSize(optional int widthOption);
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape SmallSelectedSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape filledShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float largeIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largePressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float mediumIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumPressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float smallIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallPressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSelectedRoundShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape standardShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xLargeIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargePressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xSmallIconSize;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallPressedShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSelectedRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSelectedSquareShape;
     property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSquareShape;
     field public static final androidx.compose.material3.IconButtonDefaults INSTANCE;
   }
@@ -1209,28 +1232,31 @@
   }
 
   public final class IconButtonKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledTonalIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledTonalIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledTonalIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void FilledTonalIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledTonalIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @Deprecated @androidx.compose.runtime.Composable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @Deprecated @androidx.compose.runtime.Composable public static void IconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void OutlinedIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void OutlinedIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.material3.IconToggleButtonShapes shapes, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void OutlinedIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class IconButtonShapes {
-    ctor public IconButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
-    method public androidx.compose.ui.graphics.Shape getCheckedShape();
+    ctor public IconButtonShapes(androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape pressedShape);
+    method public androidx.compose.material3.IconButtonShapes copy(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape);
     method public androidx.compose.ui.graphics.Shape getPressedShape();
     method public androidx.compose.ui.graphics.Shape getShape();
-    property public final androidx.compose.ui.graphics.Shape checkedShape;
     property public final androidx.compose.ui.graphics.Shape pressedShape;
     property public final androidx.compose.ui.graphics.Shape shape;
   }
@@ -1259,6 +1285,17 @@
     property public final long disabledContentColor;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final class IconToggleButtonShapes {
+    ctor public IconToggleButtonShapes(androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.IconToggleButtonShapes copy(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
+    method public androidx.compose.ui.graphics.Shape getCheckedShape();
+    method public androidx.compose.ui.graphics.Shape getPressedShape();
+    method public androidx.compose.ui.graphics.Shape getShape();
+    property public final androidx.compose.ui.graphics.Shape checkedShape;
+    property public final androidx.compose.ui.graphics.Shape pressedShape;
+    property public final androidx.compose.ui.graphics.Shape shape;
+  }
+
   public final class InputChipDefaults {
     method public float getAvatarSize();
     method public float getHeight();
@@ -1739,13 +1776,13 @@
   }
 
   @androidx.compose.runtime.Immutable public final class OutlinedTextFieldDefaults {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
+    method @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void ContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method public androidx.compose.foundation.layout.PaddingValues contentPadding(optional float start, optional float top, optional float end, optional float bottom);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method public float getFocusedBorderThickness();
     method public float getMinHeight();
     method public float getMinWidth();
@@ -2599,14 +2636,14 @@
   }
 
   @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
+    method @androidx.compose.runtime.Composable public void Container(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void ContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithLabel(optional float start, optional float end, optional float top, optional float bottom);
     method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithoutLabel(optional float start, optional float top, optional float end, optional float bottom);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldLabelPosition labelPosition, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.TextFieldLabelScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
     method @Deprecated public float getFocusedBorderThickness();
     method public float getFocusedIndicatorThickness();
@@ -2616,7 +2653,7 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method @Deprecated public float getUnfocusedBorderThickness();
     method public float getUnfocusedIndicatorThickness();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldColors? colors, optional androidx.compose.ui.graphics.Shape? textFieldShape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
+    method public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional androidx.compose.material3.TextFieldColors? colors, optional androidx.compose.ui.graphics.Shape? textFieldShape, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues outlinedTextFieldPadding(optional float start, optional float top, optional float end, optional float bottom);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues textFieldWithLabelPadding(optional float start, optional float end, optional float top, optional float bottom);
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
index c4350f2..2221027 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
@@ -42,7 +42,6 @@
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Narrow
 import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Wide
-import androidx.compose.material3.IconButtonShapes
 import androidx.compose.material3.IconToggleButton
 import androidx.compose.material3.OutlinedIconButton
 import androidx.compose.material3.OutlinedIconToggleButton
@@ -514,19 +513,19 @@
         ) {
             Spacer(Modifier.width(76.dp))
 
-            FilledIconButton(onClick = {}) {
+            FilledIconButton(onClick = {}, shapes = IconButtonDefaults.shapes()) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
 
-            FilledTonalIconButton(onClick = {}) {
+            FilledTonalIconButton(onClick = {}, shapes = IconButtonDefaults.shapes()) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
 
-            OutlinedIconButton(onClick = {}) {
+            OutlinedIconButton(onClick = {}, shapes = IconButtonDefaults.shapes()) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
 
-            IconButton(onClick = {}) {
+            IconButton(onClick = {}, shapes = IconButtonDefaults.shapes()) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
         }
@@ -555,12 +554,7 @@
             FilledIconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
@@ -568,12 +562,7 @@
             FilledTonalIconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
@@ -581,12 +570,7 @@
             OutlinedIconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
@@ -594,12 +578,7 @@
             IconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Outlined.Edit, contentDescription = "Localized description")
             }
@@ -629,12 +608,7 @@
             FilledIconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Filled.Edit, contentDescription = "Localized description")
             }
@@ -642,12 +616,7 @@
             FilledTonalIconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Filled.Edit, contentDescription = "Localized description")
             }
@@ -655,12 +624,7 @@
             OutlinedIconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Filled.Edit, contentDescription = "Localized description")
             }
@@ -668,12 +632,7 @@
             IconToggleButton(
                 checked = checked,
                 onCheckedChange = { checked = it },
-                shapes =
-                    IconButtonShapes(
-                        shape = IconButtonDefaults.smallRoundShape,
-                        pressedShape = IconButtonDefaults.smallPressedShape,
-                        checkedShape = IconButtonDefaults.smallSquareShape
-                    )
+                shapes = IconButtonDefaults.toggleableShapes()
             ) {
                 Icon(Icons.Filled.Edit, contentDescription = "Localized description")
             }
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ToggleButtonDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ToggleButtonDemos.kt
index d879b2e..4ea8d32 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ToggleButtonDemos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ToggleButtonDemos.kt
@@ -114,9 +114,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XSmallContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XSmallSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XSmallPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XSmallCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XSmallContentPadding
         ) {
@@ -145,9 +145,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.MediumContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.MediumSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.MediumPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.MediumCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.MediumContentPadding
         ) {
@@ -166,9 +166,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.LargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.LargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.LargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.LargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.LargeContentPadding
         ) {
@@ -187,9 +187,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XLargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XLargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XLargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XLargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XLargeContentPadding
         ) {
@@ -220,9 +220,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XSmallContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XSmallSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XSmallPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XSmallCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XSmallContentPadding
         ) {
@@ -251,9 +251,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.MediumContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.MediumSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.MediumPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.MediumCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.MediumContentPadding
         ) {
@@ -272,9 +272,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.LargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.LargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.LargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.LargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.LargeContentPadding
         ) {
@@ -293,9 +293,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XLargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XLargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XLargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XLargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XLargeContentPadding
         ) {
@@ -326,9 +326,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XSmallContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XSmallSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XSmallPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XSmallCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XSmallContentPadding
         ) {
@@ -357,9 +357,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.MediumContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.MediumSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.MediumPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.MediumCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.MediumContentPadding
         ) {
@@ -378,9 +378,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.LargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.LargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.LargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.LargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.LargeContentPadding
         ) {
@@ -399,9 +399,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XLargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XLargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XLargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XLargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XLargeContentPadding
         ) {
@@ -432,9 +432,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XSmallContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XSmallSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XSmallPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XSmallCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XSmallContentPadding
         ) {
@@ -463,9 +463,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.MediumContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.MediumSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.MediumPressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.MediumCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.MediumContentPadding
         ) {
@@ -484,9 +484,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.LargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.LargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.LargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.LargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.LargeContentPadding
         ) {
@@ -505,9 +505,9 @@
             modifier = Modifier.heightIn(ButtonDefaults.XLargeContainerHeight),
             shapes =
                 ToggleButtonDefaults.shapes(
-                    ToggleButtonDefaults.XLargeSquareShape,
+                    ToggleButtonDefaults.shape,
                     ToggleButtonDefaults.XLargePressedShape,
-                    ToggleButtonDefaults.checkedShape
+                    ToggleButtonDefaults.XLargeCheckedSquareShape
                 ),
             contentPadding = ButtonDefaults.XLargeContentPadding
         ) {
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
index 14ccfa7..bccd1f3 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
@@ -29,7 +29,6 @@
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
-import androidx.compose.material3.IconButtonShapes
 import androidx.compose.material3.IconToggleButton
 import androidx.compose.material3.OutlinedIconButton
 import androidx.compose.material3.OutlinedIconToggleButton
@@ -57,6 +56,16 @@
 @Preview
 @Sampled
 @Composable
+fun IconButtonWithAnimatedShapeSample() {
+    IconButton(onClick = { /* doSomething() */ }, shapes = IconButtonDefaults.shapes()) {
+        Icon(Icons.Filled.Lock, contentDescription = "Localized description")
+    }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
 fun XSmallNarrowSquareIconButtonsSample() {
     // Small narrow round icon button
     FilledIconButton(
@@ -146,12 +155,17 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Preview
 @Sampled
 @Composable
 fun IconToggleButtonWithAnimatedShapeSample() {
     var checked by remember { mutableStateOf(false) }
-    IconToggleButton(checked = checked, onCheckedChange = { checked = it }) {
+    IconToggleButton(
+        checked = checked,
+        onCheckedChange = { checked = it },
+        shapes = IconButtonDefaults.toggleableShapes()
+    ) {
         if (checked) {
             Icon(Icons.Filled.Lock, contentDescription = "Localized description")
         } else {
@@ -169,6 +183,16 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun FilledIconButtonWithAnimatedShapeSample() {
+    FilledIconButton(onClick = { /* doSomething() */ }, shapes = IconButtonDefaults.shapes()) {
+        Icon(Icons.Filled.Lock, contentDescription = "Localized description")
+    }
+}
+
 @Preview
 @Sampled
 @Composable
@@ -192,12 +216,7 @@
     FilledIconToggleButton(
         checked = checked,
         onCheckedChange = { checked = it },
-        shapes =
-            IconButtonShapes(
-                shape = IconButtonDefaults.smallRoundShape,
-                pressedShape = IconButtonDefaults.smallPressedShape,
-                checkedShape = IconButtonDefaults.smallSquareShape,
-            )
+        shapes = IconButtonDefaults.toggleableShapes()
     ) {
         if (checked) {
             Icon(Icons.Filled.Lock, contentDescription = "Localized description")
@@ -216,6 +235,16 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun FilledTonalIconButtonWithAnimatedShapeSample() {
+    FilledTonalIconButton(onClick = { /* doSomething() */ }, shapes = IconButtonDefaults.shapes()) {
+        Icon(Icons.Filled.Lock, contentDescription = "Localized description")
+    }
+}
+
 @Preview
 @Sampled
 @Composable
@@ -239,12 +268,7 @@
     FilledTonalIconToggleButton(
         checked = checked,
         onCheckedChange = { checked = it },
-        shapes =
-            IconButtonShapes(
-                shape = IconButtonDefaults.smallRoundShape,
-                pressedShape = IconButtonDefaults.smallPressedShape,
-                checkedShape = IconButtonDefaults.smallSquareShape,
-            )
+        shapes = IconButtonDefaults.toggleableShapes()
     ) {
         if (checked) {
             Icon(Icons.Filled.Lock, contentDescription = "Localized description")
@@ -263,6 +287,16 @@
     }
 }
 
+@ExperimentalMaterial3ExpressiveApi
+@Preview
+@Sampled
+@Composable
+fun OutlinedIconButtonWithAnimatedShapeSample() {
+    OutlinedIconButton(onClick = { /* doSomething() */ }, shapes = IconButtonDefaults.shapes()) {
+        Icon(Icons.Filled.Lock, contentDescription = "Localized description")
+    }
+}
+
 @Preview
 @Sampled
 @Composable
@@ -286,12 +320,7 @@
     OutlinedIconToggleButton(
         checked = checked,
         onCheckedChange = { checked = it },
-        shapes =
-            IconButtonShapes(
-                shape = IconButtonDefaults.smallRoundShape,
-                pressedShape = IconButtonDefaults.smallPressedShape,
-                checkedShape = IconButtonDefaults.smallSquareShape,
-            )
+        shapes = IconButtonDefaults.toggleableShapes()
     ) {
         if (checked) {
             Icon(Icons.Filled.Lock, contentDescription = "Localized description")
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
index dbb49b3..1b5532c 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
@@ -757,6 +757,47 @@
             )
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun filledIconButton_medium_pressedShape() {
+        lateinit var shape: Shape
+        val backgroundColor = Color.Yellow
+        val shapeColor = Color.Blue
+        rule.setMaterialContent(lightColorScheme()) {
+            shape = IconButtonDefaults.mediumPressedShape
+            Surface(color = backgroundColor) {
+                FilledIconButton(
+                    onClick = { /* doSomething() */ },
+                    shapes =
+                        IconButtonShapes(
+                            shape = IconButtonDefaults.mediumRoundShape,
+                            pressedShape = IconButtonDefaults.mediumPressedShape
+                        ),
+                    modifier =
+                        Modifier.testTag(IconTestTag)
+                            .size(IconButtonDefaults.mediumContainerSize()),
+                    colors =
+                        IconButtonDefaults.iconButtonVibrantColors(
+                            containerColor = shapeColor,
+                            contentColor = shapeColor
+                        ),
+                ) {}
+            }
+        }
+        rule.onNodeWithTag(IconTestTag).performTouchInput { down(center) }
+
+        rule
+            .onNodeWithTag(IconTestTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = shape,
+                shapeColor = shapeColor,
+                backgroundColor = backgroundColor,
+                antiAliasingGap = with(rule.density) { 1.dp.toPx() }
+            )
+    }
+
     @Test
     fun filledIconButton_sizeWithoutMinTargetEnforcement() {
         rule
@@ -1039,7 +1080,7 @@
                         checked = true,
                         onCheckedChange = { /* doSomething() */ },
                         shapes =
-                            IconButtonShapes(
+                            IconToggleButtonShapes(
                                 shape = IconButtonDefaults.mediumSquareShape,
                                 pressedShape = IconButtonDefaults.mediumPressedShape,
                                 checkedShape = IconButtonDefaults.mediumSquareShape
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
index 9a22720..920d5b8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
@@ -166,6 +166,84 @@
     interactionSource: MutableInteractionSource? = null,
     shape: Shape = IconButtonDefaults.standardShape,
     content: @Composable () -> Unit
+) =
+    IconButtonImpl(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        colors = colors,
+        interactionSource = interactionSource,
+        shape = shape,
+        content = content
+    )
+
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external"
+ * target="_blank">Material Design standard icon button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * ![Standard icon button
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_icon_button_round_enabled_pressed.png)
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
+ * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
+ *
+ * Simple Usage
+ *
+ * @sample androidx.compose.material3.samples.IconButtonWithAnimatedShapeSample
+ * @param onClick called when this icon button is clicked
+ * @param shapes the [IconButtonShapes] that the icon button will morph between depending on the
+ *   user's interaction with the icon button.
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ *   not respond to user input, and it will appear visually disabled and disabled to accessibility
+ *   services.
+ * @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
+ *   button in different states. See [IconButtonDefaults.iconButtonVibrantColors] and
+ *   [IconButtonDefaults.iconButtonColors] .
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ *   emitting [Interaction]s for this icon button. You can use this to change the icon button's
+ *   appearance or preview the icon button in different states. Note that if `null` is provided,
+ *   interactions will still happen internally.
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+fun IconButton(
+    onClick: () -> Unit,
+    shapes: IconButtonShapes,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
+    interactionSource: MutableInteractionSource? = null,
+    content: @Composable () -> Unit
+) {
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+    IconButtonImpl(
+        modifier,
+        onClick = onClick,
+        enabled = enabled,
+        shape = shapeForInteraction(shapes, interactionSource),
+        colors = colors,
+        interactionSource = interactionSource,
+        content = content
+    )
+}
+
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+private fun IconButtonImpl(
+    modifier: Modifier,
+    onClick: () -> Unit,
+    enabled: Boolean,
+    shape: Shape,
+    colors: IconButtonColors,
+    interactionSource: MutableInteractionSource?,
+    content: @Composable () -> Unit
 ) {
     @Suppress("NAME_SHADOWING")
     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
@@ -314,17 +392,17 @@
  * compact button is required, such as in a toolbar or image list.
  *
  * ![Standard icon toggle button
- * image](https://developer.android.com/images/reference/androidx/compose/material3/standard-icon-toggle-button.png)
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_icon_button_round_unselected_select.png)
  *
  * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
  * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
  * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
  *
  * @sample androidx.compose.material3.samples.IconToggleButtonWithAnimatedShapeSample
- * @param checked whether this icon button is toggled on or off
+ * @param checked whether this button is toggled on or off
  * @param onCheckedChange called when this icon button is clicked
- * @param shapes the [IconButtonShapes] that the icon toggle button will morph between depending on
- *   the user's interaction with the icon toggle button.
+ * @param shapes the [IconToggleButtonShapes] that the icon toggle button will morph between
+ *   depending on the user's interaction with this button.
  * @param modifier the [Modifier] to be applied to this icon button
  * @param enabled controls the enabled state of this icon button. When `false`, this component will
  *   not respond to user input, and it will appear visually disabled and disabled to accessibility
@@ -342,7 +420,7 @@
 fun IconToggleButton(
     checked: Boolean,
     onCheckedChange: (Boolean) -> Unit,
-    shapes: IconButtonShapes,
+    shapes: IconToggleButtonShapes,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonVibrantColors(),
@@ -454,6 +532,60 @@
 
 /**
  * <a href="https://m3.material.io/components/icon-button/overview" class="external"
+ * target="_blank">Material Design filled icon button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * ![Filled icon button
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_filled_icon_button_round_enabled_pressed.png)
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
+ * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
+ *
+ * Filled icon button sample:
+ *
+ * @sample androidx.compose.material3.samples.FilledIconButtonWithAnimatedShapeSample
+ * @param onClick called when this icon button is clicked
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ *   not respond to user input, and it will appear visually disabled and disabled to accessibility
+ *   services.
+ * @param shapes the [IconButtonShapes] that the icon button will morph between depending on the
+ *   user's interaction with the icon button.
+ * @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
+ *   button in different states. See [IconButtonDefaults.filledIconButtonColors].
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ *   emitting [Interaction]s for this icon button. You can use this to change the icon button's
+ *   appearance or preview the icon button in different states. Note that if `null` is provided,
+ *   interactions will still happen internally.
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+fun FilledIconButton(
+    onClick: () -> Unit,
+    shapes: IconButtonShapes,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    colors: IconButtonColors = IconButtonDefaults.filledIconButtonColors(),
+    interactionSource: MutableInteractionSource? = null,
+    content: @Composable () -> Unit
+) =
+    SurfaceIconButton(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        shapes = shapes,
+        colors = colors,
+        border = null,
+        interactionSource = interactionSource,
+        content = content
+    )
+
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external"
  * target="_blank">Material Design filled icon toggle button</a>.
  *
  * Icon buttons help people take supplementary actions with a single tap. They’re used when a
@@ -515,7 +647,7 @@
  * compact button is required, such as in a toolbar or image list.
  *
  * ![Filled icon toggle button
- * image](https://developer.android.com/images/reference/androidx/compose/material3/filled-icon-toggle-button.png)
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_filled_icon_button_round_unselected_select.png)
  *
  * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
  * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
@@ -526,8 +658,8 @@
  * @sample androidx.compose.material3.samples.FilledIconToggleButtonWithAnimatedShapeSample
  * @param checked whether this icon button is toggled on or off
  * @param onCheckedChange called when this icon button is clicked
- * @param shapes the [IconButtonShapes] that the icon toggle button will morph between depending on
- *   the user's interaction with the icon toggle button.
+ * @param shapes the [IconButtonShapes] that the icon button will morph between depending on the
+ *   user's interaction with the icon button.
  * @param modifier the [Modifier] to be applied to this icon button
  * @param enabled controls the enabled state of this icon button. When `false`, this component will
  *   not respond to user input, and it will appear visually disabled and disabled to accessibility
@@ -545,7 +677,7 @@
 fun FilledIconToggleButton(
     checked: Boolean,
     onCheckedChange: (Boolean) -> Unit,
-    shapes: IconButtonShapes,
+    shapes: IconToggleButtonShapes,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     colors: IconToggleButtonColors = IconButtonDefaults.filledIconToggleButtonColors(),
@@ -622,6 +754,64 @@
 
 /**
  * <a href="https://m3.material.io/components/icon-button/overview" class="external"
+ * target="_blank">Material Design filled tonal icon button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * ![Filled tonal icon button
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_tonal_filled_icon_button_round_enabled_pressed.png)
+ *
+ * A filled tonal icon button is a medium-emphasis icon button that is an alternative middle ground
+ * between the default [FilledIconButton] and [OutlinedIconButton]. They can be used in contexts
+ * where the lower-priority icon button requires slightly more emphasis than an outline would give.
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
+ * an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
+ *
+ * Filled tonal icon button sample:
+ *
+ * @sample androidx.compose.material3.samples.FilledTonalIconButtonWithAnimatedShapeSample
+ * @param onClick called when this icon button is clicked
+ * @param shapes the [IconButtonShapes] that the icon button will morph between depending on the
+ *   user's interaction with the icon button.
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ *   not respond to user input, and it will appear visually disabled and disabled to accessibility
+ *   services.
+ * @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
+ *   button in different states. See [IconButtonDefaults.filledIconButtonColors].
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ *   emitting [Interaction]s for this icon button. You can use this to change the icon button's
+ *   appearance or preview the icon button in different states. Note that if `null` is provided,
+ *   interactions will still happen internally.
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+fun FilledTonalIconButton(
+    onClick: () -> Unit,
+    shapes: IconButtonShapes,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    colors: IconButtonColors = IconButtonDefaults.filledTonalIconButtonColors(),
+    interactionSource: MutableInteractionSource? = null,
+    content: @Composable () -> Unit
+) =
+    SurfaceIconButton(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        shapes = shapes,
+        colors = colors,
+        border = null,
+        interactionSource = interactionSource,
+        content = content
+    )
+
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external"
  * target="_blank">Material Design filled tonal icon toggle button</a>.
  *
  * Icon buttons help people take supplementary actions with a single tap. They’re used when a
@@ -688,7 +878,7 @@
  * compact button is required, such as in a toolbar or image list.
  *
  * ![Filled tonal icon toggle button
- * image](https://developer.android.com/images/reference/androidx/compose/material3/filled-tonal-icon-toggle-button.png)
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_tonal_filled_icon_button_round_unselected_select.png)
  *
  * A filled tonal toggle icon button is a medium-emphasis icon button that is an alternative middle
  * ground between the default [FilledIconToggleButton] and [OutlinedIconToggleButton]. They can be
@@ -704,8 +894,8 @@
  * @sample androidx.compose.material3.samples.FilledTonalIconToggleButtonWithAnimatedShapeSample
  * @param checked whether this icon button is toggled on or off
  * @param onCheckedChange called when this icon button is clicked
- * @param shapes the [IconButtonShapes] that the icon toggle button will morph between depending on
- *   the user's interaction with the icon toggle button.
+ * @param shapes the [IconButtonShapes] that the icon button will morph between depending on the
+ *   user's interaction with the icon button.
  * @param modifier the [Modifier] to be applied to this icon button
  * @param enabled controls the enabled state of this icon button. When `false`, this component will
  *   not respond to user input, and it will appear visually disabled and disabled to accessibility
@@ -723,7 +913,7 @@
 fun FilledTonalIconToggleButton(
     checked: Boolean,
     onCheckedChange: (Boolean) -> Unit,
-    shapes: IconButtonShapes,
+    shapes: IconToggleButtonShapes,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     colors: IconToggleButtonColors = IconButtonDefaults.filledTonalIconToggleButtonColors(),
@@ -810,6 +1000,71 @@
 
 /**
  * <a href="https://m3.material.io/components/icon-button/overview" class="external"
+ * target="_blank">Material Design outlined icon button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * ![Outlined icon button
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_outlined_icon_button_round_enabled_pressed.png)
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * Use this "contained" icon button when the component requires more visual separation from the
+ * background.
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp. The outlined icon
+ * button has an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
+ *
+ * Toggleable filled tonal icon button with animatable shape sample:
+ *
+ * @sample androidx.compose.material3.samples.OutlinedIconButtonWithAnimatedShapeSample
+ * @param shapes the [IconButtonShapes] that the icon button will morph between depending on the
+ *   user's interaction with the icon button.
+ * @param onClick called when this icon button is clicked
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ *   not respond to user input, and it will appear visually disabled and disabled to accessibility
+ *   services.
+ * @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
+ *   button in different states. See [IconButtonDefaults.outlinedIconButtonVibrantColors] and
+ *   [IconButtonDefaults.outlinedIconButtonColors].
+ * @param border the border to draw around the container of this icon button. Pass `null` for no
+ *   border. See [IconButtonDefaults.outlinedIconButtonBorder] and
+ *   [IconButtonDefaults.outlinedIconButtonBorder].
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ *   emitting [Interaction]s for this icon button. You can use this to change the icon button's
+ *   appearance or preview the icon button in different states. Note that if `null` is provided,
+ *   interactions will still happen internally.
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+fun OutlinedIconButton(
+    onClick: () -> Unit,
+    shapes: IconButtonShapes,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    colors: IconButtonColors = IconButtonDefaults.outlinedIconButtonColors(),
+    border: BorderStroke? = IconButtonDefaults.outlinedIconButtonBorder(enabled),
+    interactionSource: MutableInteractionSource? = null,
+    content: @Composable () -> Unit
+) =
+    SurfaceIconButton(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        shapes = shapes,
+        colors = colors,
+        border = border,
+        interactionSource = interactionSource,
+        content = content
+    )
+
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external"
  * target="_blank">Material Design outlined icon toggle button</a>.
  *
  * Icon buttons help people take supplementary actions with a single tap. They’re used when a
@@ -875,7 +1130,7 @@
  * compact button is required, such as in a toolbar or image list.
  *
  * ![Outlined icon toggle button
- * image](https://developer.android.com/images/reference/androidx/compose/material3/outlined-icon-toggle-button.png)
+ * image](https://developer.android.com/images/reference/androidx/compose/material3/small_outlined_icon_button_round_unselected_select.png)
  *
  * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
  * custom icon, note that the typical size for the internal icon is 24 x 24 dp. This icon button has
@@ -884,8 +1139,8 @@
  * @sample androidx.compose.material3.samples.OutlinedIconToggleButtonWithAnimatedShapeSample
  * @param checked whether this icon button is toggled on or off
  * @param onCheckedChange called when this icon button is clicked
- * @param shapes the [IconButtonShapes] that the icon toggle button will morph between depending on
- *   the user's interaction with the icon toggle button.
+ * @param shapes the [IconButtonShapes] that the icon button will morph between depending on the
+ *   user's interaction with the icon button.
  * @param modifier the [Modifier] to be applied to this icon button
  * @param enabled controls the enabled state of this icon button. When `false`, this component will
  *   not respond to user input, and it will appear visually disabled and disabled to accessibility
@@ -905,7 +1160,7 @@
 fun OutlinedIconToggleButton(
     checked: Boolean,
     onCheckedChange: (Boolean) -> Unit,
-    shapes: IconButtonShapes,
+    shapes: IconToggleButtonShapes,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     colors: IconToggleButtonColors = IconButtonDefaults.outlinedIconToggleButtonVibrantColors(),
@@ -956,6 +1211,34 @@
         }
     }
 
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+private fun SurfaceIconButton(
+    onClick: () -> Unit,
+    modifier: Modifier,
+    enabled: Boolean,
+    shapes: IconButtonShapes,
+    colors: IconButtonColors,
+    border: BorderStroke?,
+    interactionSource: MutableInteractionSource?,
+    content: @Composable () -> Unit
+) {
+
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+
+    SurfaceIconButton(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        shape = shapeForInteraction(shapes, interactionSource),
+        colors = colors,
+        border = border,
+        interactionSource = interactionSource,
+        content = content
+    )
+}
+
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun SurfaceIconToggleButton(
@@ -1020,7 +1303,7 @@
     onCheckedChange: (Boolean) -> Unit,
     modifier: Modifier,
     enabled: Boolean,
-    shapes: IconButtonShapes,
+    shapes: IconToggleButtonShapes,
     colors: IconToggleButtonColors,
     border: BorderStroke?,
     interactionSource: MutableInteractionSource?,
@@ -1229,20 +1512,45 @@
     }
 }
 
+/**
+ * The shapes that will be used in icon buttons. Icon button will morph between these shapes
+ * depending on the interaction of the icon button, assuming all of the shapes are
+ * [CornerBasedShape]s.
+ *
+ * @property shape is the unchecked shape.
+ * @property pressedShape is the pressed shape.
+ */
 @ExperimentalMaterial3ExpressiveApi
-@Composable
-private fun shapeForInteraction(
-    checked: Boolean,
-    shapes: IconButtonShapes,
-    interactionSource: MutableInteractionSource,
-): Shape {
-    // TODO Load the motionScheme tokens from the component tokens file
-    // MotionSchemeKeyTokens.DefaultEffects is intentional here to prevent
-    // any bounce in this component.
-    val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Float>()
-    val pressed by interactionSource.collectIsPressedAsState()
+class IconButtonShapes(val shape: Shape, val pressedShape: Shape = shape) {
 
-    return shapeByInteraction(shapes, pressed, checked, defaultAnimationSpec)
+    /** Returns a copy of this IconButtonShapes, optionally overriding some of the values. */
+    fun copy(
+        shape: Shape? = this.shape,
+        pressedShape: Shape? = this.pressedShape,
+    ) =
+        IconButtonShapes(
+            shape = shape.takeOrElse { this.shape },
+            pressedShape = pressedShape.takeOrElse { this.pressedShape },
+        )
+
+    internal fun Shape?.takeOrElse(block: () -> Shape): Shape = this ?: block()
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || other !is IconButtonShapes) return false
+
+        if (shape != other.shape) return false
+        if (pressedShape != other.pressedShape) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = shape.hashCode()
+        result = 31 * result + pressedShape.hashCode()
+
+        return result
+    }
 }
 
 /**
@@ -1255,11 +1563,29 @@
  * @property checkedShape is the checked shape.
  */
 @ExperimentalMaterial3ExpressiveApi
-class IconButtonShapes(val shape: Shape, val pressedShape: Shape, val checkedShape: Shape) {
+class IconToggleButtonShapes(
+    val shape: Shape,
+    val pressedShape: Shape = shape,
+    val checkedShape: Shape = shape
+) {
+
+    /** Returns a copy of this IconButtonShapes, optionally overriding some of the values. */
+    fun copy(
+        shape: Shape? = this.shape,
+        pressedShape: Shape? = this.pressedShape,
+        checkedShape: Shape? = this.checkedShape
+    ) =
+        IconToggleButtonShapes(
+            shape = shape.takeOrElse { this.shape },
+            pressedShape = pressedShape.takeOrElse { this.pressedShape },
+            checkedShape = checkedShape.takeOrElse { this.checkedShape }
+        )
+
+    internal fun Shape?.takeOrElse(block: () -> Shape): Shape = this ?: block()
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
-        if (other == null || other !is IconButtonShapes) return false
+        if (other == null || other !is IconToggleButtonShapes) return false
 
         if (shape != other.shape) return false
         if (pressedShape != other.pressedShape) return false
@@ -1277,18 +1603,85 @@
     }
 }
 
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+private fun shapeForInteraction(
+    shapes: IconButtonShapes,
+    interactionSource: MutableInteractionSource,
+): Shape {
+    if (shapes.isStatic) {
+        return shapes.shape
+    }
+    // TODO Load the motionScheme tokens from the component tokens file
+    // MotionSchemeKeyTokens.DefaultEffects is intentional here to prevent
+    // any bounce in this component.
+    val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Float>()
+    val pressed by interactionSource.collectIsPressedAsState()
+
+    return shapeByInteraction(shapes, pressed, defaultAnimationSpec)
+}
+
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+private fun shapeForInteraction(
+    checked: Boolean,
+    shapes: IconToggleButtonShapes,
+    interactionSource: MutableInteractionSource,
+): Shape {
+    if (shapes.isStatic) {
+        return shapes.shape
+    }
+    // TODO Load the motionScheme tokens from the component tokens file
+    // MotionSchemeKeyTokens.DefaultEffects is intentional here to prevent
+    // any bounce in this component.
+    val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Float>()
+    val pressed by interactionSource.collectIsPressedAsState()
+
+    return shapeByInteraction(shapes, pressed, checked, defaultAnimationSpec)
+}
+
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 internal val IconButtonShapes.isCornerBasedShape: Boolean
+    get() = shape is RoundedCornerShape && pressedShape is CornerBasedShape
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+internal val IconButtonShapes.isStatic: Boolean
+    get() = shape === pressedShape
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+internal val IconToggleButtonShapes.isCornerBasedShape: Boolean
     get() =
         shape is RoundedCornerShape &&
             pressedShape is CornerBasedShape &&
             checkedShape is CornerBasedShape
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+internal val IconToggleButtonShapes.isStatic: Boolean
+    get() = shape === pressedShape && shape === checkedShape
+
 @ExperimentalMaterial3ExpressiveApi
 @Composable
 private fun shapeByInteraction(
     shapes: IconButtonShapes,
     pressed: Boolean,
+    animationSpec: FiniteAnimationSpec<Float>
+): Shape {
+    val shape =
+        if (pressed) {
+            shapes.pressedShape
+        } else shapes.shape
+
+    if (shapes.isCornerBasedShape) {
+        return key(shapes) { rememberAnimatedShape(shape as RoundedCornerShape, animationSpec) }
+    }
+    return shape
+}
+
+@ExperimentalMaterial3ExpressiveApi
+@Composable
+private fun shapeByInteraction(
+    shapes: IconToggleButtonShapes,
+    pressed: Boolean,
     checked: Boolean,
     animationSpec: FiniteAnimationSpec<Float>
 ): Shape {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButtonDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButtonDefaults.kt
index 6f0cdd8..3cf5445 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButtonDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButtonDefaults.kt
@@ -887,6 +887,20 @@
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3ExpressiveApi
     @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any extra small icon button. */
+    val xSmallSelectedRoundShape: Shape
+        @Composable get() = XSmallIconButtonTokens.SelectedContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any extra small, square icon button. */
+    val xSmallSelectedSquareShape: Shape
+        @Composable get() = XSmallIconButtonTokens.SelectedContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
     /** Default shape for any small icon button. */
     val smallRoundShape: Shape
         @Composable get() = SmallIconButtonTokens.ContainerShapeRound.value
@@ -894,7 +908,7 @@
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3ExpressiveApi
     @ExperimentalMaterial3ExpressiveApi
-    /** Default shape for any small icon button. */
+    /** Default square shape for any small icon button. */
     val smallSquareShape: Shape
         @Composable get() = SmallIconButtonTokens.ContainerShapeSquare.value
 
@@ -908,6 +922,20 @@
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3ExpressiveApi
     @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any small icon button. */
+    val smallSelectedRoundShape: Shape
+        @Composable get() = SmallIconButtonTokens.SelectedContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any small, square icon button. */
+    val SmallSelectedSquareShape: Shape
+        @Composable get() = SmallIconButtonTokens.SelectedContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
     /** Default shape for any medium icon button. */
     val mediumRoundShape: Shape
         @Composable get() = MediumIconButtonTokens.ContainerShapeRound.value
@@ -929,6 +957,20 @@
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3ExpressiveApi
     @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any medium icon button. */
+    val mediumSelectedRoundShape: Shape
+        @Composable get() = MediumIconButtonTokens.SelectedContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any medium, square icon button. */
+    val mediumSelectedSquareShape: Shape
+        @Composable get() = MediumIconButtonTokens.SelectedContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
     /** Default shape for any large icon button. */
     val largeRoundShape: Shape
         @Composable get() = LargeIconButtonTokens.ContainerShapeRound.value
@@ -950,6 +992,20 @@
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
     @get:ExperimentalMaterial3ExpressiveApi
     @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any large icon button. */
+    val largeSelectedRoundShape: Shape
+        @Composable get() = LargeIconButtonTokens.SelectedContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any large, square icon button. */
+    val largeSelectedSquareShape: Shape
+        @Composable get() = LargeIconButtonTokens.SelectedContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
     /** Default shape for any xlarge icon button. */
     val xLargeRoundShape: Shape
         @Composable get() = XLargeIconButtonTokens.ContainerShapeRound.value
@@ -968,20 +1024,100 @@
     val xLargePressedShape: Shape
         @Composable get() = XLargeIconButtonTokens.PressedContainerShape.value
 
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any extra large icon button. */
+    val xLargeSelectedRoundShape: Shape
+        @Composable get() = XSmallIconButtonTokens.SelectedContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default selected shape for any extra large, square icon button. */
+    val xLargeSelectedSquareShape: Shape
+        @Composable get() = XLargeIconButtonTokens.SelectedContainerShapeSquare.value
+
     /**
-     * Creates a [ButtonShapes] that correspond to the shapes in the default, pressed, and checked
-     * states. Toggle button will morph between these shapes as long as the shapes are all
+     * Creates a [IconButtonShapes] that correspond to the shapes in the default or pressed states.
+     * Icon button will morph between these shapes as long as the shapes are all
      * [CornerBasedShape]s.
      *
      * @param shape the unchecked shape for [ButtonShapes]
      * @param pressedShape the unchecked shape for [ButtonShapes]
-     * @param checkedShape the unchecked shape for [ButtonShapes]
      */
     @ExperimentalMaterial3ExpressiveApi
     @Composable
-    fun shapes(shape: Shape, pressedShape: Shape, checkedShape: Shape): IconButtonShapes =
-        remember(shape, pressedShape, checkedShape) {
-            IconButtonShapes(shape, pressedShape, checkedShape)
+    fun shapes(shape: Shape? = null, pressedShape: Shape? = null): IconButtonShapes =
+        MaterialTheme.shapes.defaultIconButtonShapes.copy(
+            shape = shape,
+            pressedShape = pressedShape,
+        )
+
+    /**
+     * Creates a [IconButtonShapes] that correspond to a default [IconButton] in the active and
+     * pressed states. [IconButton] will morph between these shapes as long as the shapes are all
+     * [CornerBasedShape]s.
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    @Composable
+    fun shapes(): IconButtonShapes = MaterialTheme.shapes.defaultIconButtonShapes
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    internal val Shapes.defaultIconButtonShapes: IconButtonShapes
+        @Composable
+        get() {
+            return defaultIconButtonShapesCached
+                ?: IconButtonShapes(
+                        shape = smallRoundShape,
+                        pressedShape = smallPressedShape,
+                    )
+                    .also { defaultIconButtonShapesCached = it }
+        }
+
+    /**
+     * Creates a [IconToggleButtonShapes] that correspond to the shapes in the default, pressed, and
+     * checked states. Icon button will morph between these shapes as long as the shapes are all
+     * [CornerBasedShape]s.
+     *
+     * @param shape the active shape for [IconToggleButtonShapes]
+     * @param pressedShape the pressed shape for [IconToggleButtonShapes]
+     * @param checkedShape the checked shape for [IconToggleButtonShapes]
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    @Composable
+    fun toggleableShapes(
+        shape: Shape? = null,
+        pressedShape: Shape? = null,
+        checkedShape: Shape? = null
+    ): IconToggleButtonShapes =
+        MaterialTheme.shapes.defaultIconToggleButtonShapes.copy(
+            shape = shape,
+            pressedShape = pressedShape,
+            checkedShape = checkedShape
+        )
+
+    /**
+     * Creates a [ButtonShapes] that correspond to a default [IconToggleButton] in the active,
+     * pressed and selected states. [IconToggleButton] will morph between these shapes as long as
+     * the shapes are all [CornerBasedShape]s.
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    @Composable
+    fun toggleableShapes(): IconToggleButtonShapes =
+        MaterialTheme.shapes.defaultIconToggleButtonShapes
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    internal val Shapes.defaultIconToggleButtonShapes: IconToggleButtonShapes
+        @Composable
+        get() {
+            return defaultIconToggleButtonShapesCached
+                ?: IconToggleButtonShapes(
+                        shape = smallRoundShape,
+                        pressedShape = smallPressedShape,
+                        checkedShape = smallSelectedRoundShape
+                    )
+                    .also { defaultIconToggleButtonShapesCached = it }
         }
 
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
index 4c8d388..87abf31 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
@@ -264,6 +264,10 @@
     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     internal var defaultToggleButtonShapesCached: ToggleButtonShapes? = null
     internal var defaultVerticalDragHandleShapesCached: DragHandleShapes? = null
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    internal var defaultIconToggleButtonShapesCached: IconToggleButtonShapes? = null
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    internal var defaultIconButtonShapesCached: IconButtonShapes? = null
 }
 
 /** Contains the default values used by [Shapes] */
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
index 17f68b4..60c27f0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
@@ -87,9 +87,8 @@
     val FocusedIndicatorThickness = 2.dp
 
     /**
-     * A decorator used to create custom text fields based on <a
-     * href="https://m3.material.io/components/text-fields/overview" class="external"
-     * target="_blank">Material Design filled text field</a>.
+     * A decorator used to create custom text fields based on
+     * [Material Design filled text field](https://m3.material.io/components/text-fields/overview).
      *
      * If your text field requires customising elements that aren't exposed by [TextField], such as
      * the indicator line thickness, consider using this decorator to achieve the desired design.
@@ -141,7 +140,6 @@
      *   [Container]. Default colors for the container come from the [colors].
      */
     @Composable
-    @ExperimentalMaterial3Api
     fun decorator(
         state: TextFieldState,
         enabled: Boolean,
@@ -212,9 +210,9 @@
 
     /**
      * Composable that draws a default container for a [TextField] with an indicator line at the
-     * bottom. You can apply it to a [BasicTextField] using [DecorationBox] to create a custom text
-     * field based on the styling of a Material filled text field. The [TextField] component applies
-     * it automatically.
+     * bottom. You can apply it to a [BasicTextField] using [decorator] or [DecorationBox] to create
+     * a custom text field based on the styling of a Material filled text field. The [TextField]
+     * component applies it automatically.
      *
      * @param enabled whether the text field is enabled
      * @param isError whether the text field's current value is in error
@@ -228,7 +226,6 @@
      * @param unfocusedIndicatorLineThickness thickness of the indicator line when the text field is
      *   not focused
      */
-    @ExperimentalMaterial3Api
     @Composable
     fun Container(
         enabled: Boolean,
@@ -264,8 +261,8 @@
 
     /**
      * A modifier to draw a default bottom indicator line for [TextField]. You can apply it to a
-     * [BasicTextField] or to [DecorationBox] to create a custom text field based on the styling of
-     * a Material filled text field.
+     * [BasicTextField] to create a custom text field based on the styling of a Material filled text
+     * field.
      *
      * Consider using [Container], which automatically applies this modifier as well as other text
      * field container styling.
@@ -283,7 +280,6 @@
      * @param unfocusedIndicatorLineThickness thickness of the indicator line when the text field is
      *   not focused
      */
-    @ExperimentalMaterial3Api
     fun Modifier.indicatorLine(
         enabled: Boolean,
         isError: Boolean,
@@ -305,9 +301,8 @@
             )
 
     /**
-     * A decoration box used to create custom text fields based on <a
-     * href="https://m3.material.io/components/text-fields/overview" class="external"
-     * target="_blank">Material Design filled text field</a>.
+     * A decoration box used to create custom text fields based on
+     * [Material Design filled text field](https://m3.material.io/components/text-fields/overview).
      *
      * If your text field requires customising elements that aren't exposed by [TextField], consider
      * using this decoration box to achieve the desired design.
@@ -323,9 +318,9 @@
      *
      * @sample androidx.compose.material3.samples.CustomTextFieldBasedOnDecorationBox
      * @param value the input [String] shown by the text field
-     * @param innerTextField input text field that this decoration box wraps. You will pass here a
-     *   framework-controlled composable parameter "innerTextField" from the decorationBox lambda of
-     *   the [BasicTextField]
+     * @param innerTextField input text field that this decoration box wraps. Pass the
+     *   framework-controlled composable parameter `innerTextField` from the `decorationBox` lambda
+     *   of the [BasicTextField]
      * @param enabled the enabled state of the text field. When `false`, this decoration box will
      *   appear visually disabled. This must be the same value that is passed to [BasicTextField].
      * @param singleLine indicates if this is a single line or multi line text field. This must be
@@ -363,7 +358,6 @@
      *   [Container]. Default colors for the container come from the [colors].
      */
     @Composable
-    @ExperimentalMaterial3Api
     fun DecorationBox(
         value: String,
         innerTextField: @Composable () -> Unit,
@@ -917,9 +911,8 @@
     val FocusedBorderThickness = 2.dp
 
     /**
-     * A decorator used to create custom text fields based on <a
-     * href="https://m3.material.io/components/text-fields/overview" class="external"
-     * target="_blank">Material Design outlined text field</a>.
+     * A decorator used to create custom text fields based on
+     * [Material Design outlined text field](https://m3.material.io/components/text-fields/overview).
      *
      * If your text field requires customising elements that aren't exposed by [OutlinedTextField],
      * such as the border thickness, consider using this decorator to achieve the desired design.
@@ -972,7 +965,6 @@
      *   [colors].
      */
     @Composable
-    @ExperimentalMaterial3Api
     fun decorator(
         state: TextFieldState,
         enabled: Boolean,
@@ -1038,9 +1030,9 @@
 
     /**
      * Composable that draws a default container for an [OutlinedTextField] with a border stroke.
-     * You can apply it to a [BasicTextField] using [DecorationBox] to create a custom text field
-     * based on the styling of a Material outlined text field. The [OutlinedTextField] component
-     * applies it automatically.
+     * You can apply it to a [BasicTextField] using [decorator] or [DecorationBox] to create a
+     * custom text field based on the styling of a Material outlined text field. The
+     * [OutlinedTextField] component applies it automatically.
      *
      * @param enabled whether the text field is enabled
      * @param isError whether the text field's current value is in error
@@ -1052,7 +1044,6 @@
      * @param focusedBorderThickness thickness of the border when the text field is focused
      * @param unfocusedBorderThickness thickness of the border when the text field is not focused
      */
-    @ExperimentalMaterial3Api
     @Composable
     fun Container(
         enabled: Boolean,
@@ -1088,9 +1079,8 @@
     }
 
     /**
-     * A decoration box used to create custom text fields based on <a
-     * href="https://m3.material.io/components/text-fields/overview" class="external"
-     * target="_blank">Material Design outlined text field</a>.
+     * A decoration box used to create custom text fields based on
+     * [Material Design outlined text field](https://m3.material.io/components/text-fields/overview).
      *
      * If your text field requires customising elements that aren't exposed by [OutlinedTextField],
      * consider using this decoration box to achieve the desired design.
@@ -1106,9 +1096,9 @@
      *
      * @sample androidx.compose.material3.samples.CustomOutlinedTextFieldBasedOnDecorationBox
      * @param value the input [String] shown by the text field
-     * @param innerTextField input text field that this decoration box wraps. You will pass here a
-     *   framework-controlled composable parameter "innerTextField" from the decorationBox lambda of
-     *   the [BasicTextField]
+     * @param innerTextField input text field that this decoration box wraps. Pass the
+     *   framework-controlled composable parameter `innerTextField` from the `decorationBox` lambda
+     *   of the [BasicTextField]
      * @param enabled the enabled state of the text field. When `false`, this decoration box will
      *   appear visually disabled. This must be the same value that is passed to [BasicTextField].
      * @param singleLine indicates if this is a single line or multi line text field. This must be
@@ -1146,7 +1136,6 @@
      *   [colors].
      */
     @Composable
-    @ExperimentalMaterial3Api
     fun DecorationBox(
         value: String,
         innerTextField: @Composable () -> Unit,
diff --git a/compose/ui/ui/api/1.8.0-beta01.txt b/compose/ui/ui/api/1.8.0-beta01.txt
index b2b5534..a710625 100644
--- a/compose/ui/ui/api/1.8.0-beta01.txt
+++ b/compose/ui/ui/api/1.8.0-beta01.txt
@@ -2578,7 +2578,7 @@
   }
 
   public final class OnGlobalLayoutListenerKt {
-    method public static kotlinx.coroutines.DisposableHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2591,7 +2591,7 @@
 
   public final class OnLayoutRectChangedModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier onLayoutRectChanged(androidx.compose.ui.Modifier, optional long throttleMillis, optional long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
-    method public static kotlinx.coroutines.DisposableHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnPlacedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2857,6 +2857,10 @@
     property public abstract androidx.compose.ui.Modifier.Node node;
   }
 
+  public static fun interface DelegatableNode.RegistrationHandle {
+    method public void unregister();
+  }
+
   public final class DelegatableNodeKt {
     method public static void invalidateSubtree(androidx.compose.ui.node.DelegatableNode);
     method public static void requestAutofill(androidx.compose.ui.node.DelegatableNode);
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index b2b5534..a710625 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2578,7 +2578,7 @@
   }
 
   public final class OnGlobalLayoutListenerKt {
-    method public static kotlinx.coroutines.DisposableHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2591,7 +2591,7 @@
 
   public final class OnLayoutRectChangedModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier onLayoutRectChanged(androidx.compose.ui.Modifier, optional long throttleMillis, optional long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
-    method public static kotlinx.coroutines.DisposableHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnPlacedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2857,6 +2857,10 @@
     property public abstract androidx.compose.ui.Modifier.Node node;
   }
 
+  public static fun interface DelegatableNode.RegistrationHandle {
+    method public void unregister();
+  }
+
   public final class DelegatableNodeKt {
     method public static void invalidateSubtree(androidx.compose.ui.node.DelegatableNode);
     method public static void requestAutofill(androidx.compose.ui.node.DelegatableNode);
diff --git a/compose/ui/ui/api/restricted_1.8.0-beta01.txt b/compose/ui/ui/api/restricted_1.8.0-beta01.txt
index f381fac..c19065a 100644
--- a/compose/ui/ui/api/restricted_1.8.0-beta01.txt
+++ b/compose/ui/ui/api/restricted_1.8.0-beta01.txt
@@ -2586,7 +2586,7 @@
   }
 
   public final class OnGlobalLayoutListenerKt {
-    method public static kotlinx.coroutines.DisposableHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2599,7 +2599,7 @@
 
   public final class OnLayoutRectChangedModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier onLayoutRectChanged(androidx.compose.ui.Modifier, optional long throttleMillis, optional long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
-    method public static kotlinx.coroutines.DisposableHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnPlacedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2911,6 +2911,10 @@
     property public abstract androidx.compose.ui.Modifier.Node node;
   }
 
+  public static fun interface DelegatableNode.RegistrationHandle {
+    method public void unregister();
+  }
+
   public final class DelegatableNodeKt {
     method public static void invalidateSubtree(androidx.compose.ui.node.DelegatableNode);
     method public static void requestAutofill(androidx.compose.ui.node.DelegatableNode);
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index f381fac..c19065a 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2586,7 +2586,7 @@
   }
 
   public final class OnGlobalLayoutListenerKt {
-    method public static kotlinx.coroutines.DisposableHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnGlobalLayoutListener(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2599,7 +2599,7 @@
 
   public final class OnLayoutRectChangedModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier onLayoutRectChanged(androidx.compose.ui.Modifier, optional long throttleMillis, optional long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
-    method public static kotlinx.coroutines.DisposableHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
+    method public static androidx.compose.ui.node.DelegatableNode.RegistrationHandle registerOnLayoutRectChanged(androidx.compose.ui.node.DelegatableNode, long throttleMillis, long debounceMillis, kotlin.jvm.functions.Function1<? super androidx.compose.ui.spatial.RelativeLayoutBounds,kotlin.Unit> callback);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface OnPlacedModifier extends androidx.compose.ui.Modifier.Element {
@@ -2911,6 +2911,10 @@
     property public abstract androidx.compose.ui.Modifier.Node node;
   }
 
+  public static fun interface DelegatableNode.RegistrationHandle {
+    method public void unregister();
+  }
+
   public final class DelegatableNodeKt {
     method public static void invalidateSubtree(androidx.compose.ui.node.DelegatableNode);
     method public static void requestAutofill(androidx.compose.ui.node.DelegatableNode);
diff --git a/compose/ui/ui/src/androidInstrumentedTest/AndroidManifest.xml b/compose/ui/ui/src/androidInstrumentedTest/AndroidManifest.xml
index 4ff61f6..a7e46bd 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/AndroidManifest.xml
+++ b/compose/ui/ui/src/androidInstrumentedTest/AndroidManifest.xml
@@ -21,6 +21,9 @@
             android:name="androidx.compose.ui.test.TestActivity"
             android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen" />
         <activity
+            android:name="androidx.compose.ui.test.TestActivity2"
+            android:theme="@android:style/Theme.Material.Light.NoActionBar" />
+        <activity
             android:name="androidx.compose.ui.window.ActivityWithInsets"
             android:windowSoftInputMode="adjustResize"
             android:theme="@android:style/Theme.Material.Light.NoActionBar" />
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusListenerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusListenerTest.kt
index 12b5593..46d756d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusListenerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusListenerTest.kt
@@ -27,8 +27,11 @@
 import androidx.compose.ui.ComposeUiFlags
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.InputMode
+import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.node.requireSemanticsInfo
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.semanticsId
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
@@ -101,7 +104,9 @@
         // Arrange.
         val listener = TestFocusListener()
         lateinit var focusManager: FocusManager
+        lateinit var inputModeManager: InputModeManager
         rule.setContent(listener) {
+            inputModeManager = LocalInputModeManager.current
             focusManager = LocalFocusManager.current
             Box(Modifier.size(10.dp).testTag("item").focusable())
         }
@@ -117,7 +122,10 @@
             assertThat(listener)
                 .isEqualTo(
                     TestFocusListener(
-                        if (initialFocusAfterClearFocus) {
+                        if (
+                            initialFocusAfterClearFocus ||
+                                inputModeManager.inputMode == InputMode.Keyboard
+                        ) {
                             mutableListOf(Pair(itemId, null), Pair(null, itemId))
                         } else {
                             mutableListOf(Pair(itemId, null))
@@ -159,7 +167,9 @@
         // Arrange.
         val listener = TestFocusListener()
         lateinit var focusManager: FocusManager
+        lateinit var inputModeManager: InputModeManager
         rule.setContent(listener) {
+            inputModeManager = LocalInputModeManager.current
             focusManager = LocalFocusManager.current
             Column {
                 Box(Modifier.size(10.dp).testTag("item1").focusable())
@@ -180,7 +190,10 @@
             assertThat(listener)
                 .isEqualTo(
                     TestFocusListener(
-                        if (initialFocusAfterClearFocus) {
+                        if (
+                            initialFocusAfterClearFocus ||
+                                inputModeManager.inputMode == InputMode.Keyboard
+                        ) {
                             mutableListOf(Pair(item2Id, null), Pair(null, item1Id))
                         } else {
                             mutableListOf(Pair(item2Id, null))
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index 67dcaf4..02e6454 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -48,6 +48,7 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
@@ -83,6 +84,7 @@
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
@@ -97,6 +99,7 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
+import kotlin.math.ceil
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
@@ -121,6 +124,11 @@
 
     private lateinit var container: OpenComposeView
 
+    private fun roundUpDpToNearestHundred(dpValue: Dp): Dp {
+        val roundedValue = ceil(dpValue.value / 100f).toInt() * 100
+        return roundedValue.dp
+    }
+
     @Before
     fun setup() {
         val activity = rule.activity
@@ -4733,6 +4741,1312 @@
     }
 
     /**
+     * Child of a **clipped** parent uses .graphicsLayer { translationY } to offset OUTSIDE the
+     * parent's clipped dimensions. Any touch input outside the clipped parent (including both
+     * direct hits and indirect hits in the minimum touch target) should not be triggered in any
+     * children. This test calculates the edges using the minimum touch target size (48.dp).
+     */
+    @Test
+    fun clippedWithMinimumTouchTargetOverlap_shouldNotTriggerOverlappingClippedTouch() {
+        val eventLog = mutableListOf<PointerEventType>()
+        var innerOffsetBoxCoordinates: LayoutCoordinates? = null
+        var parentBoxCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(2)
+
+        var dpInPixel: Float? = null
+
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalDensity.current) { dpInPixel = 1.dp.toPx() }
+                Column(Modifier.background(Color.Cyan).fillMaxSize()) {
+                    // Top Box
+                    Box(Modifier.background(Color.Gray).size(100.dp))
+                    // Bottom Box (parent)
+                    Box(
+                        Modifier.background(Color.Red)
+                            .size(40.dp)
+                            .clipToBounds()
+                            .onGloballyPositioned {
+                                parentBoxCoordinates = it
+                                latch.countDown()
+                            }
+                    ) {
+                        // Inner Bottom Box (this clipped child is the main box we are testing)
+                        Box(
+                            Modifier.size(40.dp)
+                                .background(Color.Green)
+                                // Moves the box outside of the clipped area of the parent
+                                .graphicsLayer { translationY = -10.dp.roundToPx().toFloat() }
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        while (true) {
+                                            val event = awaitPointerEvent()
+                                            event.changes[0].consume()
+                                            eventLog += event.type
+                                        }
+                                    }
+                                }
+                                .onGloballyPositioned {
+                                    innerOffsetBoxCoordinates = it
+                                    latch.countDown()
+                                }
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        val offsetBoxCoords: LayoutCoordinates = innerOffsetBoxCoordinates!!
+        val parentBoxCoords: LayoutCoordinates = parentBoxCoordinates!!
+
+        val justOutsideMinimumTouchTargetOfClippedChild = dpInPixel!! * 5
+        val edgeOfMinimumTouchTargetOfClippedChild = dpInPixel!! * 4
+
+        // Hits the top Box, but just outside the minimum touch target area of the bottom child Box
+        // (which includes the offset into top box).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfClippedChild)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfClippedChild)
+        )
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Hits the top Box and the minimum touch target area edge of the bottom child Box (which
+        // includes the offset into top box). Despite this being in the minimum touch area, it will
+        // NOT trigger the event since it's in the clipped region.
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfClippedChild)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfClippedChild)
+        )
+
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Hits the top Box and a direct hit of the edge of the bottom child Box (which
+        // includes the offset into top box). It will NOT trigger the event since it's in the
+        // clipped region.
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords)
+
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Hits the top Box and a direct hit of the bottom child Box (which
+        // includes the offset into top box). It's one dp shy of the edge of the parent bottom box.
+        // It will NOT trigger the event since it's still in the clipped region.
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords, Offset(0f, -dpInPixel!!))
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords, Offset(0f, -dpInPixel!!))
+
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Direct hit of edge of bottom parent box (and child box) in an unclipped region.
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords)
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords)
+
+        rule.runOnUiThread {
+            assertThat(eventLog)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+
+        // Hits the bottom box in the unclipped region (farther down from edges).
+        val topOfUnclipped = Offset(0f, (offsetBoxCoords.size.height / 2 + 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, topOfUnclipped)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, topOfUnclipped)
+
+        // Continue to the bottom of the bottom Box
+        val bottomOfBox = Offset(0f, (offsetBoxCoords.size.height - 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, bottomOfBox)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, bottomOfBox)
+
+        // Now exit the bottom box
+        val justBelow = Offset(0f, (offsetBoxCoords.size.height + 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, justBelow)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, justBelow)
+
+        rule.runOnUiThread {
+            assertThat(eventLog)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+    }
+
+    /**
+     * Same as clippedWithMinimumTouchTargetOverlap_shouldNotTriggerOverlappingClippedTouch() but
+     * uses .offset() instead of .graphicsLayer { translationY }.
+     */
+    @Test
+    fun clippedWithMinimumTouchTargetOverlapViaOffset_shouldNotTriggerOverlappingClippedTouch() {
+        val eventLog = mutableListOf<PointerEventType>()
+        var innerOffsetBoxCoordinates: LayoutCoordinates? = null
+        var parentBoxCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(2)
+
+        var dpInPixel: Float? = null
+
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalDensity.current) { dpInPixel = 1.dp.toPx() }
+                Column(Modifier.background(Color.Cyan).fillMaxSize()) {
+                    // Top Box
+                    Box(Modifier.background(Color.Gray).size(100.dp))
+                    // Bottom Box (parent)
+                    Box(
+                        Modifier.background(Color.Red)
+                            .size(40.dp)
+                            .clipToBounds()
+                            .onGloballyPositioned {
+                                parentBoxCoordinates = it
+                                latch.countDown()
+                            }
+                    ) {
+                        // Inner Bottom Box (this clipped child is the main box we are testing)
+                        Box(
+                            Modifier.size(40.dp)
+                                .background(Color.Green)
+                                // Moves the box outside of the clipped area of the parent
+                                .offset(x = 0.dp, y = (-10).dp)
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        while (true) {
+                                            val event = awaitPointerEvent()
+                                            event.changes[0].consume()
+                                            eventLog += event.type
+                                        }
+                                    }
+                                }
+                                .onGloballyPositioned {
+                                    innerOffsetBoxCoordinates = it
+                                    latch.countDown()
+                                }
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        val offsetBoxCoords: LayoutCoordinates = innerOffsetBoxCoordinates!!
+        val parentBoxCoords: LayoutCoordinates = parentBoxCoordinates!!
+
+        val justOutsideMinimumTouchTargetOfClippedChild = dpInPixel!! * 5
+        val edgeOfMinimumTouchTargetOfClippedChild = dpInPixel!! * 4
+
+        // Hits the top Box, but just outside the minimum touch target area of the bottom child Box
+        // (which includes the offset into top box).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfClippedChild)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfClippedChild)
+        )
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Hit the top Box, but in the minimum touch target area of the bottom Box (that hit area
+        // is WITHIN the clipped region but minimum area hit trumps clip and it will be triggered).
+        // Note: This is not a direct hit of the bottom box.
+
+        // Hits the top Box and the minimum touch target area edge of the bottom child Box (which
+        // includes the offset into top box). Despite this being in the minimum touch area, it will
+        // NOT trigger the event since it's in the clipped region.
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfClippedChild)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfClippedChild)
+        )
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Hits the top Box and a direct hit of the edge of the bottom child Box (which
+        // includes the offset into top box). It will NOT trigger the event since it's in the
+        // clipped region.
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords)
+
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Hits the top Box and a direct hit of the bottom child Box (which
+        // includes the offset into top box). It's one dp shy of the edge of the parent bottom box.
+        // It will NOT trigger the event since it's still in the clipped region.
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords, Offset(0f, -dpInPixel!!))
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords, Offset(0f, -dpInPixel!!))
+
+        rule.runOnUiThread { assertThat(eventLog).isEmpty() }
+
+        // Direct hit of edge of bottom parent box (and child box) in an unclipped region.
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords)
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords)
+
+        rule.runOnUiThread {
+            assertThat(eventLog)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+
+        // Hits the bottom box in the unclipped region (farther down from edges).
+        val topOfUnclipped = Offset(0f, (offsetBoxCoords.size.height / 2 + 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, topOfUnclipped)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, topOfUnclipped)
+
+        // Continue to the bottom of the bottom Box
+        val bottomOfBox = Offset(0f, (offsetBoxCoords.size.height - 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, bottomOfBox)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, bottomOfBox)
+
+        // Now exit the bottom box
+        val justBelow = Offset(0f, (offsetBoxCoords.size.height + 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, justBelow)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, justBelow)
+
+        rule.runOnUiThread {
+            assertThat(eventLog)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+    }
+
+    /**
+     * Child of a parent uses .graphicsLayer { translationY } to offset OUTSIDE the parent's
+     * dimensions. Any touch input outside the parent must be a direct hit to win (not indirect in
+     * minimum test target size). This test calculates the edges using the minimum touch target size
+     * (48.dp). Also, tests pruning a one node tree in HitTestResult.
+     *
+     * TODO(jjw): Write test for this in lower level test file.
+     */
+    @Test
+    fun minimumTouchTargetOverlap_triggersDirectHitWithHigherOrder() {
+        val eventLogTopBox = mutableListOf<PointerEventType>()
+        val eventLogBottomBox = mutableListOf<PointerEventType>()
+        var innerOffsetBoxCoordinates: LayoutCoordinates? = null
+        var parentBoxCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(2)
+
+        var dpInPixel: Float? = null
+
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalDensity.current) { dpInPixel = 1.dp.toPx() }
+                Column(Modifier.background(Color.Cyan).fillMaxSize()) {
+                    // Top Box
+                    Box(
+                        Modifier.background(Color.Gray).size(100.dp).pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent()
+                                    event.changes[0].consume()
+                                    eventLogTopBox += event.type
+                                }
+                            }
+                        }
+                    )
+                    // Bottom Box (parent)
+                    Box(
+                        Modifier.background(Color.Red).size(40.dp).onGloballyPositioned {
+                            parentBoxCoordinates = it
+                            latch.countDown()
+                        }
+                    ) {
+                        // Inner Bottom Box (main box we are testing)
+                        Box(
+                            Modifier.size(40.dp)
+                                .background(Color.Green)
+                                // Moves the box outside of the parent
+                                .graphicsLayer { translationY = -10.dp.roundToPx().toFloat() }
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        while (true) {
+                                            val event = awaitPointerEvent()
+                                            event.changes[0].consume()
+                                            eventLogBottomBox += event.type
+                                        }
+                                    }
+                                }
+                                .onGloballyPositioned {
+                                    innerOffsetBoxCoordinates = it
+                                    latch.countDown()
+                                }
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        val offsetBoxCoords: LayoutCoordinates = innerOffsetBoxCoordinates!!
+        val parentBoxCoords: LayoutCoordinates = parentBoxCoordinates!!
+
+        val justOutsideMinimumTouchTargetOfChildBox = dpInPixel!! * 5
+        val edgeOfMinimumTouchTargetOfChildBox = dpInPixel!! * 4
+
+        // Hits the top Box, but just outside the minimum touch target area of the bottom child Box
+        // (which includes the offset into top box).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfChildBox)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfChildBox)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox).isEmpty()
+        }
+
+        // Hit the top Box, but in the minimum touch target area of the bottom Box. Because this is
+        // not a direct hit on the bottom box, the top box still wins.
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfChildBox)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfChildBox)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox).isEmpty()
+        }
+
+        // Hits the top Box and a direct hit of the edge of the bottom child Box (which
+        // includes the offset into top box). Bottom child will get the event since it has higher
+        // order of the two direct hits.
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords)
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox)
+                .containsExactly(
+                    PointerEventType.Enter,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+
+        // Still a direct hit on bottom child box, so it wins.
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords, Offset(0f, -dpInPixel!!))
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords, Offset(0f, -dpInPixel!!))
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox)
+                .containsExactly(
+                    PointerEventType.Enter,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+
+        // Direct hit of edge of bottom parent box (and child box).
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords)
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords)
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox)
+                .containsExactly(
+                    PointerEventType.Enter,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+
+        // Hits the bottom box (farther down from edges).
+        val topOfUnclipped = Offset(0f, (offsetBoxCoords.size.height / 2 + 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, topOfUnclipped)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, topOfUnclipped)
+
+        // Continue to the bottom of the bottom Box
+        val bottomOfBox = Offset(0f, (offsetBoxCoords.size.height - 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, bottomOfBox)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, bottomOfBox)
+
+        // Now exit the bottom box
+        val justBelow = Offset(0f, (offsetBoxCoords.size.height + 1).toFloat())
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords, justBelow)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords, justBelow)
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox)
+                .containsExactly(
+                    PointerEventType.Enter,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+    }
+
+    /**
+     * Tests proper pointer input results on three nested boxes with different dimensions:
+     * - Large box 5x the size of the small box
+     *     - Medium box 3x size of small box (centered in large box as child)
+     *         - Small box larger than minimum touch target size (centered in medium box as child)
+     *
+     * In this test, [Modifier.offset()] is used to center the boxes! The test is meant to test
+     * modifiers that change the LayoutNode's location while also being large enough to not hit any
+     * logic associated with the minimum touch target size.
+     *
+     * Input hits top left area of large box (triggers large box), the top left area of the medium
+     * box (triggers large and medium), and the center of the small box (triggers all three boxes).
+     *
+     * This test is a high-level implementation of the tests using
+     * [PointerInputEventProcessorTest.process_partialTreeHits].
+     */
+    @Test
+    fun inputOnNestedBoxesLargerThanMinTouchPlacedViaOffset_simpleInput_properlyTriggers() {
+        val eventLogLargeBox = mutableListOf<PointerEventType>()
+        val eventLogMediumBox = mutableListOf<PointerEventType>()
+        val eventLogSmallBox = mutableListOf<PointerEventType>()
+
+        var layoutCoordsLargeBox: LayoutCoordinates? = null
+
+        // Changes dynamically to size specified by minimumTouchTargetSize.
+        var minimumTouchTargetSizeDp: Dp
+
+        var dimensionsLargeBoxDp: Dp
+        var dimensionsMediumBoxDp: Dp
+        var dimensionsSmallBoxDp: Dp
+
+        var offsetAmountDp: Dp
+
+        var hitAllThreeBoxesFloat: Float? = null
+        var hitLargeAndMediumBoxesFloat: Float? = null
+        var hitLargeBoxOnlyFloat: Float? = null
+
+        val latch = CountDownLatch(1)
+
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalViewConfiguration.current) {
+                    minimumTouchTargetSizeDp = this.minimumTouchTargetSize.width
+                }
+
+                with(LocalDensity.current) {
+                    val baseSize = roundUpDpToNearestHundred(minimumTouchTargetSizeDp)
+                    dimensionsSmallBoxDp = baseSize
+                    dimensionsMediumBoxDp = baseSize * 3
+                    dimensionsLargeBoxDp = baseSize * 5
+
+                    // Just happens to be the same dimensions as the bottom box
+                    offsetAmountDp = dimensionsSmallBoxDp
+
+                    hitAllThreeBoxesFloat = dimensionsLargeBoxDp.toPx() / 2
+                    hitLargeAndMediumBoxesFloat = dimensionsMediumBoxDp.toPx() / 2
+                    hitLargeBoxOnlyFloat = dimensionsSmallBoxDp.toPx() / 2
+                }
+
+                // Large Box (5x the size of the small box)
+                Box(
+                    Modifier.background(Color.Cyan)
+                        .size(dimensionsLargeBoxDp)
+                        .pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent()
+                                    event.changes[0].consume()
+                                    eventLogLargeBox += event.type
+                                }
+                            }
+                        }
+                        .onGloballyPositioned {
+                            layoutCoordsLargeBox = it
+                            latch.countDown()
+                        }
+                ) {
+                    // Medium Box (3x the size of the small box)
+                    Box(
+                        Modifier.offset(offsetAmountDp, offsetAmountDp)
+                            .background(Color.Gray)
+                            .size(dimensionsMediumBoxDp)
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        val event = awaitPointerEvent()
+                                        event.changes[0].consume()
+                                        eventLogMediumBox += event.type
+                                    }
+                                }
+                            }
+                    ) {
+                        // Small Box
+                        Box(
+                            Modifier.offset(offsetAmountDp, offsetAmountDp)
+                                .background(Color.Red)
+                                .size(dimensionsSmallBoxDp)
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        while (true) {
+                                            val event = awaitPointerEvent()
+                                            event.changes[0].consume()
+                                            eventLogSmallBox += event.type
+                                        }
+                                    }
+                                }
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        val topOffsetBoxCoords: LayoutCoordinates = layoutCoordsLargeBox!!
+
+        // Hits the large box only (outside of the medium and small boxes [child, grandchild]).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitLargeBoxOnlyFloat!!, hitLargeBoxOnlyFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitLargeBoxOnlyFloat!!, hitLargeBoxOnlyFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox).isEmpty()
+            assertThat(eventLogSmallBox).isEmpty()
+        }
+
+        // Hits the medium boxes inside large box (but of small box [child]).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitLargeAndMediumBoxesFloat!!, hitLargeAndMediumBoxesFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitLargeAndMediumBoxesFloat!!, hitLargeAndMediumBoxesFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogSmallBox).isEmpty()
+        }
+
+        // Hits the small boxes inside medium box inside large box (that is, hits all).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitAllThreeBoxesFloat!!, hitAllThreeBoxesFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitAllThreeBoxesFloat!!, hitAllThreeBoxesFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogSmallBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+    }
+
+    /**
+     * Same test as
+     * [inputOnNestedBoxesLargerThanMinTouchPlacedViaOffset_simpleInput_properlyTriggers()] but uses
+     * [Modifier.graphicsLayer()] instead of [Modifier.offset()] to move the LayoutNodes.
+     */
+    @Test
+    fun inputOnNestedBoxesLargerThanMinTouchPlacedViaGraphicsLayer_simpleInput_properlyTriggers() {
+        val eventLogLargeBox = mutableListOf<PointerEventType>()
+        val eventLogMediumBox = mutableListOf<PointerEventType>()
+        val eventLogSmallBox = mutableListOf<PointerEventType>()
+
+        var layoutCoordsLargeBox: LayoutCoordinates? = null
+
+        // Changes dynamically to size specified by minimumTouchTargetSize.
+        var minimumTouchTargetSizeDp: Dp
+
+        var dimensionsLargeBoxDp: Dp
+        var dimensionsMediumBoxDp: Dp
+        var dimensionsSmallBoxDp: Dp
+
+        var offsetAmountDp: Dp
+
+        var hitAllThreeBoxesFloat: Float? = null
+        var hitLargeAndMediumBoxesFloat: Float? = null
+        var hitLargeBoxOnlyFloat: Float? = null
+
+        val latch = CountDownLatch(1)
+
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalViewConfiguration.current) {
+                    minimumTouchTargetSizeDp = this.minimumTouchTargetSize.width
+                }
+
+                with(LocalDensity.current) {
+                    val baseSize = roundUpDpToNearestHundred(minimumTouchTargetSizeDp)
+                    dimensionsSmallBoxDp = baseSize
+                    dimensionsMediumBoxDp = baseSize * 3
+                    dimensionsLargeBoxDp = baseSize * 5
+
+                    // Just happens to be the same dimensions as the bottom box
+                    offsetAmountDp = dimensionsSmallBoxDp
+
+                    hitAllThreeBoxesFloat = dimensionsLargeBoxDp.toPx() / 2
+                    hitLargeAndMediumBoxesFloat = dimensionsMediumBoxDp.toPx() / 2
+                    hitLargeBoxOnlyFloat = dimensionsSmallBoxDp.toPx() / 2
+                }
+
+                // Large Box (5x the size of the small box)
+                Box(
+                    Modifier.background(Color.Cyan)
+                        .size(dimensionsLargeBoxDp)
+                        .pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent()
+                                    event.changes[0].consume()
+                                    eventLogLargeBox += event.type
+                                }
+                            }
+                        }
+                        .onGloballyPositioned {
+                            layoutCoordsLargeBox = it
+                            latch.countDown()
+                        }
+                ) {
+                    // Medium Box (3x the size of the small box)
+                    Box(
+                        Modifier.graphicsLayer {
+                                translationY = offsetAmountDp.toPx()
+                                translationX = offsetAmountDp.toPx()
+                            }
+                            .background(Color.Gray)
+                            .size(dimensionsMediumBoxDp)
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        val event = awaitPointerEvent()
+                                        event.changes[0].consume()
+                                        eventLogMediumBox += event.type
+                                    }
+                                }
+                            }
+                    ) {
+                        // Small Box
+                        Box(
+                            Modifier.graphicsLayer {
+                                    translationY = offsetAmountDp.toPx()
+                                    translationX = offsetAmountDp.toPx()
+                                }
+                                .background(Color.Red)
+                                .size(dimensionsSmallBoxDp)
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        while (true) {
+                                            val event = awaitPointerEvent()
+                                            event.changes[0].consume()
+                                            eventLogSmallBox += event.type
+                                        }
+                                    }
+                                }
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        val topOffsetBoxCoords: LayoutCoordinates = layoutCoordsLargeBox!!
+
+        // Hits the large box only (outside of the medium and small boxes [child, grandchild]).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitLargeBoxOnlyFloat!!, hitLargeBoxOnlyFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitLargeBoxOnlyFloat!!, hitLargeBoxOnlyFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox).isEmpty()
+            assertThat(eventLogSmallBox).isEmpty()
+        }
+
+        // Hits the medium boxes inside large box (but of small box [child]).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitLargeAndMediumBoxesFloat!!, hitLargeAndMediumBoxesFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitLargeAndMediumBoxesFloat!!, hitLargeAndMediumBoxesFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogSmallBox).isEmpty()
+        }
+
+        // Hits the small boxes inside medium box inside large box (that is, hits all).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitAllThreeBoxesFloat!!, hitAllThreeBoxesFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitAllThreeBoxesFloat!!, hitAllThreeBoxesFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogSmallBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+    }
+
+    /**
+     * Same test as
+     * [inputOnNestedBoxesLargerThanMinTouchPlacedViaOffset_simpleInput_properlyTriggers()] but uses
+     * [Modifier.padding()] instead of [Modifier.offset()] to move the LayoutNodes.
+     */
+    @Test
+    fun inputOnNestedBoxesLargerThanMinTouchPlacedViaPadding_simpleInput_properlyTriggers() {
+        val eventLogLargeBox = mutableListOf<PointerEventType>()
+        val eventLogMediumBox = mutableListOf<PointerEventType>()
+        val eventLogSmallBox = mutableListOf<PointerEventType>()
+
+        var layoutCoordsLargeBox: LayoutCoordinates? = null
+
+        // Changes dynamically to size specified by minimumTouchTargetSize.
+        var minimumTouchTargetSizeDp: Dp
+
+        var dimensionsLargeBoxDp: Dp
+        var dimensionsMediumBoxDp: Dp
+        var dimensionsSmallBoxDp: Dp
+
+        var offsetAmountDp: Dp
+
+        var hitAllThreeBoxesFloat: Float? = null
+        var hitLargeAndMediumBoxesFloat: Float? = null
+        var hitLargeBoxOnlyFloat: Float? = null
+
+        val latch = CountDownLatch(1)
+
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalViewConfiguration.current) {
+                    minimumTouchTargetSizeDp = this.minimumTouchTargetSize.width
+                }
+
+                with(LocalDensity.current) {
+                    val baseSize = roundUpDpToNearestHundred(minimumTouchTargetSizeDp)
+                    dimensionsSmallBoxDp = baseSize
+                    dimensionsMediumBoxDp = baseSize * 3
+                    dimensionsLargeBoxDp = baseSize * 5
+
+                    // Just happens to be the same dimensions as the bottom box
+                    offsetAmountDp = dimensionsSmallBoxDp
+
+                    hitAllThreeBoxesFloat = dimensionsLargeBoxDp.toPx() / 2
+                    hitLargeAndMediumBoxesFloat = dimensionsMediumBoxDp.toPx() / 2
+                    hitLargeBoxOnlyFloat = dimensionsSmallBoxDp.toPx() / 2
+                }
+
+                // Large Box (5x the size of the small box)
+                Box(
+                    Modifier.background(Color.Cyan)
+                        .size(dimensionsLargeBoxDp)
+                        .pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent()
+                                    event.changes[0].consume()
+                                    eventLogLargeBox += event.type
+                                }
+                            }
+                        }
+                        .onGloballyPositioned {
+                            layoutCoordsLargeBox = it
+                            latch.countDown()
+                        }
+                ) {
+                    // Medium Box (3x the size of the small box)
+                    Box(
+                        Modifier.padding(start = offsetAmountDp, top = offsetAmountDp)
+                            .background(Color.Gray)
+                            .size(dimensionsMediumBoxDp)
+                            .pointerInput(Unit) {
+                                awaitPointerEventScope {
+                                    while (true) {
+                                        val event = awaitPointerEvent()
+                                        event.changes[0].consume()
+                                        eventLogMediumBox += event.type
+                                    }
+                                }
+                            }
+                    ) {
+                        // Small Box
+                        Box(
+                            Modifier.padding(start = offsetAmountDp, top = offsetAmountDp)
+                                .background(Color.Red)
+                                .size(dimensionsSmallBoxDp)
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        while (true) {
+                                            val event = awaitPointerEvent()
+                                            event.changes[0].consume()
+                                            eventLogSmallBox += event.type
+                                        }
+                                    }
+                                }
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        val topOffsetBoxCoords: LayoutCoordinates = layoutCoordsLargeBox!!
+
+        // Hits the large box only (outside of the medium and small boxes [child, grandchild]).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitLargeBoxOnlyFloat!!, hitLargeBoxOnlyFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitLargeBoxOnlyFloat!!, hitLargeBoxOnlyFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox).isEmpty()
+            assertThat(eventLogSmallBox).isEmpty()
+        }
+
+        // Hits the medium boxes inside large box (but of small box [child]).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitLargeAndMediumBoxesFloat!!, hitLargeAndMediumBoxesFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitLargeAndMediumBoxesFloat!!, hitLargeAndMediumBoxesFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogSmallBox).isEmpty()
+        }
+
+        // Hits the small boxes inside medium box inside large box (that is, hits all).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            topOffsetBoxCoords,
+            Offset(hitAllThreeBoxesFloat!!, hitAllThreeBoxesFloat!!)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            topOffsetBoxCoords,
+            Offset(hitAllThreeBoxesFloat!!, hitAllThreeBoxesFloat!!)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogLargeBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogMediumBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogSmallBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+    }
+
+    /**
+     * Same as minimumTouchTargetOverlap_triggersDirectHitWithHigherOrder() but uses .offset()
+     * instead of .graphicsLayer { translationY }.
+     */
+    @Test
+    fun minimumTouchTargetOverlapWithOffset_triggersDirectHitWithHigherOrder() {
+        val eventLogTopBox = mutableListOf<PointerEventType>()
+        val eventLogBottomBox = mutableListOf<PointerEventType>()
+        var innerOffsetBoxCoordinates: LayoutCoordinates? = null
+        var parentBoxCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(2)
+
+        var dpInPixel: Float? = null
+
+        rule.runOnUiThread {
+            container.setContent {
+                with(LocalDensity.current) { dpInPixel = 1.dp.toPx() }
+                Column(Modifier.background(Color.Cyan).fillMaxSize()) {
+                    // Top Box
+                    Box(
+                        Modifier.background(Color.Gray).size(100.dp).pointerInput(Unit) {
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent()
+                                    event.changes[0].consume()
+                                    eventLogTopBox += event.type
+                                }
+                            }
+                        }
+                    )
+                    // Bottom Box (parent)
+                    Box(
+                        Modifier.background(Color.Red).size(40.dp).onGloballyPositioned {
+                            parentBoxCoordinates = it
+                            latch.countDown()
+                        }
+                    ) {
+                        // Inner Bottom Box (main box we are testing)
+                        Box(
+                            Modifier.size(40.dp)
+                                .background(Color.Green)
+                                // Moves the box outside of the parent
+                                .offset(x = 0.dp, y = (-10).dp)
+                                .pointerInput(Unit) {
+                                    awaitPointerEventScope {
+                                        while (true) {
+                                            val event = awaitPointerEvent()
+                                            event.changes[0].consume()
+                                            eventLogBottomBox += event.type
+                                        }
+                                    }
+                                }
+                                .onGloballyPositioned {
+                                    innerOffsetBoxCoordinates = it
+                                    latch.countDown()
+                                }
+                        )
+                    }
+                }
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+
+        val offsetBoxCoords: LayoutCoordinates = innerOffsetBoxCoordinates!!
+        val parentBoxCoords: LayoutCoordinates = parentBoxCoordinates!!
+
+        val justOutsideMinimumTouchTargetOfChildBox = dpInPixel!! * 5
+        val edgeOfMinimumTouchTargetOfChildBox = dpInPixel!! * 4
+
+        // Hits the top Box, but just outside the minimum touch target area of the bottom child Box
+        // (which includes the offset into top box).
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfChildBox)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -justOutsideMinimumTouchTargetOfChildBox)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox).isEmpty()
+        }
+
+        // Hit the top Box, but in the minimum touch target area of the bottom Box. Because this is
+        // not a direct hit on the bottom box, the top box still wins.
+        dispatchTouchEvent(
+            ACTION_DOWN,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfChildBox)
+        )
+        dispatchTouchEvent(
+            ACTION_UP,
+            offsetBoxCoords,
+            Offset(0f, -edgeOfMinimumTouchTargetOfChildBox)
+        )
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox).isEmpty()
+        }
+
+        // Hits the top Box and a direct hit of the edge of the bottom child Box (which
+        // includes the offset into top box). Bottom child will get the event since it has higher
+        // order of the two direct hits.
+        dispatchMouseEvent(ACTION_DOWN, offsetBoxCoords)
+        dispatchMouseEvent(ACTION_UP, offsetBoxCoords)
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox)
+                .containsExactly(
+                    PointerEventType.Enter,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+
+        // Still a direct hit on bottom child box, so it wins.
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords, Offset(0f, -dpInPixel!!))
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords, Offset(0f, -dpInPixel!!))
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox)
+                .containsExactly(
+                    PointerEventType.Enter,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+
+        // Direct hit of edge of bottom parent box (and child box).
+        dispatchMouseEvent(ACTION_DOWN, parentBoxCoords)
+        dispatchMouseEvent(ACTION_UP, parentBoxCoords)
+
+        rule.runOnUiThread {
+            assertThat(eventLogTopBox)
+                .containsExactly(
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+            assertThat(eventLogBottomBox)
+                .containsExactly(
+                    PointerEventType.Enter,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                    PointerEventType.Press,
+                    PointerEventType.Release,
+                )
+        }
+    }
+
+    /**
      * Touch events occur between two boxes that are both less than the minimum touch target size.
      * Tests an event directly between two boxes, then on each side of that border to make sure the
      * correct box event handlers are triggered.
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListenerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListenerTest.kt
index 1bc9aff..a21cb87 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListenerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListenerTest.kt
@@ -38,6 +38,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.node.DelegatableNode.RegistrationHandle
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.requireOwner
 import androidx.compose.ui.platform.InspectorInfo
@@ -61,7 +62,6 @@
 import java.util.concurrent.TimeUnit
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
-import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
@@ -612,10 +612,10 @@
     var debounceMillis: Long,
     var callback: (RelativeLayoutBounds) -> Unit,
 ) : Modifier.Node() {
-    var handle: DisposableHandle? = null
+    var handle: RegistrationHandle? = null
 
     fun diposeAndRegister() {
-        handle?.dispose()
+        handle?.unregister()
         handle = registerOnGlobalLayoutListener(throttleMillis, debounceMillis, callback)
     }
 
@@ -624,6 +624,6 @@
     }
 
     override fun onDetach() {
-        handle?.dispose()
+        handle?.unregister()
     }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/TestActivity2.kt
similarity index 64%
copy from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt
copy to compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/TestActivity2.kt
index 1f106db..b4de468 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/TestActivity2.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2019 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.
@@ -13,17 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package androidx.compose.ui.test
 
-package androidx.compose.ui.node
+import androidx.activity.ComponentActivity
 
-internal expect class TreeSet<E>(comparator: Comparator<in E>) {
-    fun add(element: E): Boolean
-
-    fun remove(element: E): Boolean
-
-    fun first(): E
-
-    fun contains(element: E): Boolean
-
-    fun isEmpty(): Boolean
-}
+class TestActivity2 : ComponentActivity()
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/MixedFocusChangeTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/MixedFocusChangeTest.kt
new file mode 100644
index 0000000..f2e723e
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/MixedFocusChangeTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.compose.ui.viewinterop
+
+import android.content.Context
+import android.view.KeyEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.AbsoluteAlignment
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.background
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.InputMode
+import androidx.compose.ui.input.InputModeManager
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.platform.LocalInputModeManager
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.TestActivity2
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+class MixedFocusChangeTest {
+    @get:Rule val rule = createAndroidComposeRule<TestActivity2>()
+
+    @Test
+    fun siblingWithWorseBeam() {
+        val view = rule.runOnUiThread { MyComposeView(rule.activity, false) }
+        rule.runOnIdle { rule.activity.setContentView(view) }
+        rule.waitUntil {
+            view.findViewWithTag<View>("item 0") != null &&
+                view.findViewWithTag<View>("item 1") != null
+        }
+        val first = view.findViewWithTag<View>("item 0")
+        val second = view.findViewWithTag<View>("item 1")
+        rule.runOnIdle {
+            view.inputModeManager.requestInputMode(InputMode.Keyboard)
+            first.requestFocus()
+        }
+        rule.runOnIdle { assertThat(first.isFocused).isTrue() }
+        InstrumentationRegistry.getInstrumentation()
+            .sendKeySync(KeyEvent(KeyEvent.ACTION_DOWN, Key.DirectionDown.nativeKeyCode))
+        rule.runOnIdle { assertThat(second.isFocused).isTrue() }
+    }
+
+    @Test
+    fun previousEscapesRecyclerView() {
+        val view = rule.runOnUiThread { MyComposeView(rule.activity, false) }
+        rule.runOnIdle { rule.activity.setContentView(view) }
+        rule.waitUntil { view.findViewWithTag<View>("item 0") != null }
+        val first = view.findViewWithTag<View>("item 0")
+        rule.runOnIdle {
+            view.inputModeManager.requestInputMode(InputMode.Keyboard)
+            first.requestFocus()
+        }
+        rule.runOnIdle { assertThat(first.isFocused).isTrue() }
+        InstrumentationRegistry.getInstrumentation()
+            .sendKeySync(KeyEvent(KeyEvent.ACTION_DOWN, Key.NavigatePrevious.nativeKeyCode))
+        rule.onNodeWithTag(clickableBoxTag).assertIsFocused()
+    }
+
+    @Test
+    fun nextEscapesReverseRecyclerView() {
+        val view = rule.runOnUiThread { MyComposeView(rule.activity, true) }
+        rule.runOnIdle { rule.activity.setContentView(view) }
+        rule.waitUntil { view.findViewWithTag<View>("item 0") != null }
+        val first = view.findViewWithTag<View>("item 0")
+        rule.runOnIdle {
+            view.inputModeManager.requestInputMode(InputMode.Keyboard)
+            first.requestFocus()
+        }
+        rule.runOnIdle { assertThat(first.isFocused).isTrue() }
+        InstrumentationRegistry.getInstrumentation()
+            .sendKeySync(KeyEvent(KeyEvent.ACTION_DOWN, Key.NavigateNext.nativeKeyCode))
+        rule.onNodeWithTag(clickableBoxTag).assertIsFocused()
+    }
+
+    class MyComposeView(context: Context, val reverse: Boolean) : AbstractComposeView(context) {
+        lateinit var inputModeManager: InputModeManager
+        lateinit var view: View
+        lateinit var recyclerView: RecyclerView
+
+        @Composable
+        override fun Content() {
+            view = LocalView.current
+            inputModeManager = LocalInputModeManager.current
+            Box(Modifier.fillMaxSize()) {
+                AndroidView(
+                    modifier = Modifier.fillMaxSize(),
+                    factory = {
+                        RecyclerView(it).apply {
+                            recyclerView = this
+                            layoutManager =
+                                LinearLayoutManager(context, RecyclerView.VERTICAL, reverse)
+                            adapter = MyAdapter()
+                        }
+                    }
+                )
+                Box(
+                    modifier =
+                        Modifier.padding(30.dp)
+                            .width(100.dp)
+                            .height(400.dp)
+                            .background(Color.Red)
+                            .align(AbsoluteAlignment.BottomRight)
+                            .testTag(clickableBoxTag)
+                            .clickable {}
+                            .fillMaxSize()
+                ) {
+                    Text(
+                        text = "Click Me",
+                        color = Color.White,
+                        modifier = Modifier.align(Alignment.Center)
+                    )
+                }
+            }
+        }
+    }
+
+    class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
+        private val items = List(100) { "Item ${it + 1}" }
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
+            val textView =
+                TextView(parent.context).apply {
+                    layoutParams =
+                        ViewGroup.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.WRAP_CONTENT
+                        )
+                    setPadding(16, 16, 16, 16)
+                    isFocusable = true // Makes the item focusable
+                    isFocusableInTouchMode = true // Required for touch navigation
+                }
+            return MyViewHolder(textView)
+        }
+
+        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
+            (holder.itemView as TextView).text = items[position]
+            holder.itemView.tag = "item $position"
+        }
+
+        override fun getItemCount(): Int = items.size
+
+        class MyViewHolder(view: View) : RecyclerView.ViewHolder(view)
+    }
+
+    companion object {
+        private const val clickableBoxTag = "clickableBox"
+    }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 7da948c..066fda8 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -32,7 +32,6 @@
 import android.os.SystemClock
 import android.util.LongSparseArray
 import android.util.SparseArray
-import android.view.FocusFinder
 import android.view.InputDevice
 import android.view.KeyEvent as AndroidKeyEvent
 import android.view.MotionEvent
@@ -136,6 +135,8 @@
 import androidx.compose.ui.input.key.Key.Companion.DirectionRight
 import androidx.compose.ui.input.key.Key.Companion.DirectionUp
 import androidx.compose.ui.input.key.Key.Companion.Escape
+import androidx.compose.ui.input.key.Key.Companion.NavigateNext
+import androidx.compose.ui.input.key.Key.Companion.NavigatePrevious
 import androidx.compose.ui.input.key.Key.Companion.NumPadEnter
 import androidx.compose.ui.input.key.Key.Companion.PageDown
 import androidx.compose.ui.input.key.Key.Companion.PageUp
@@ -348,7 +349,7 @@
             val focusedRect = onFetchFocusRect()?.toAndroidRect()
 
             val nextView =
-                FocusFinder.getInstance().let {
+                FocusFinderCompat.instance.let {
                     if (focusedRect == null) {
                         it.findNextFocus(this, findFocus(), direction)
                     } else {
@@ -369,30 +370,17 @@
 
         val currentFocus = root.findFocus() ?: error("view hasFocus but root can't find it")
 
-        val focusFinder = FocusFinder.getInstance()
-        val nextView: View?
+        val focusFinder = FocusFinderCompat.instance
+        val nextView = focusFinder.findNextFocus(root, currentFocus, direction)
         val focusedRect: Rect?
         if (focusDirection.is1dFocusSearch() && androidViewsHandler.hasFocus()) {
             focusedRect = null
-            if (SDK_INT >= O) {
-                // On newer devices, the focus is normal and we can expect forward/next to work
-                nextView = focusFinder.findNextFocus(root, currentFocus, direction)
-            } else {
-                // On older devices, FocusFinder doesn't properly order Views, so we have to use
-                // a copy of the focus finder the corrects the order
-                nextView = FocusFinderCompat.instance.findNextFocus1d(root, currentFocus, direction)
-            }
         } else {
             focusedRect = onFetchFocusRect()?.toAndroidRect()
-            nextView = focusFinder.findNextFocusFromRect(root, focusedRect, direction)
-            nextView?.getLocationInWindow(tmpPositionArray)
-            val nextPositionX = tmpPositionArray[0]
-            val nextPositionY = tmpPositionArray[1]
-            getLocationInWindow(tmpPositionArray)
-            focusedRect?.offset(
-                tmpPositionArray[0] - nextPositionX,
-                tmpPositionArray[1] - nextPositionY
-            )
+            if (nextView != null && focusedRect != null) {
+                root.offsetDescendantRectToMyCoords(this, focusedRect)
+                root.offsetRectIntoDescendantCoords(nextView, focusedRect)
+            }
         }
 
         // is it part of the compose hierarchy?
@@ -464,13 +452,9 @@
                 val nextView = findNextNonChildView(androidDirection).takeIf { it != this }
                 if (nextView != null) {
                     val androidRect = checkNotNull(focusedRect?.toAndroidRect()) { "Invalid rect" }
-                    nextView.getLocationInWindow(tmpPositionArray)
-                    val nextX = tmpPositionArray[0]
-                    val nextY = tmpPositionArray[1]
-                    getLocationInWindow(tmpPositionArray)
-                    val currentX = tmpPositionArray[0]
-                    val currentY = tmpPositionArray[1]
-                    androidRect.offset(currentX - nextX, currentY - nextY)
+                    val rootView = rootView as ViewGroup
+                    rootView.offsetDescendantRectToMyCoords(this, androidRect)
+                    rootView.offsetRectIntoDescendantCoords(nextView, androidRect)
                     if (nextView.requestInteropFocus(androidDirection, androidRect)) {
                         return@onKeyEvent true
                     }
@@ -498,7 +482,7 @@
 
     private fun findNextNonChildView(direction: Int): View? {
         var currentView: View? = this
-        val focusFinder = FocusFinder.getInstance()
+        val focusFinder = FocusFinderCompat.instance
         while (currentView != null) {
             currentView = focusFinder.findNextFocus(rootView as ViewGroup, currentView, direction)
             if (currentView != null && !containsDescendant(currentView)) return currentView
@@ -996,7 +980,7 @@
         }
 
         // Find the next subview if any using FocusFinder.
-        val nextView = FocusFinder.getInstance().findNextFocus(this, focused, direction)
+        val nextView = FocusFinderCompat.instance.findNextFocus(this, focused, direction)
 
         // Find the next composable using FocusOwner.
         val focusedBounds =
@@ -1853,6 +1837,8 @@
 
     override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
         return when (keyEvent.key) {
+            NavigatePrevious -> Previous
+            NavigateNext -> Next
             Tab -> if (keyEvent.isShiftPressed) Previous else Next
             DirectionRight -> Right
             DirectionLeft -> Left
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/FocusFinderCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/FocusFinderCompat.android.kt
index 9a42b02..175e1eb 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/FocusFinderCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/FocusFinderCompat.android.kt
@@ -20,21 +20,32 @@
 import android.graphics.Rect
 import android.os.Build
 import android.view.View
+import android.view.View.FOCUSABLES_ALL
+import android.view.View.FOCUSABLES_TOUCH_MODE
 import android.view.View.FOCUS_BACKWARD
+import android.view.View.FOCUS_DOWN
 import android.view.View.FOCUS_FORWARD
+import android.view.View.FOCUS_LEFT
+import android.view.View.FOCUS_RIGHT
+import android.view.View.FOCUS_UP
 import android.view.ViewGroup
-import androidx.collection.MutableObjectList
-import androidx.collection.ObjectList
 import androidx.collection.mutableObjectIntMapOf
+import androidx.collection.mutableObjectListOf
 import androidx.collection.mutableScatterMapOf
 import androidx.collection.mutableScatterSetOf
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.isBetterCandidate
+import androidx.compose.ui.focus.toFocusDirection
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastForEachReversed
 import androidx.core.view.isVisible
 import java.util.Collections
 
 /**
- * On devices before [Build.VERSION_CODES.O], [FocusFinder] orders Views incorrectly. This
- * implementation uses the current [FocusFinder] behavior, ordering Views correctly for
- * one-dimensional focus searches.
+ * FocusFinder behaves differently and sometimes incorrectly on different API versions. This is a
+ * consistent version of FocusFinder used in AndroidComposeView
  *
  * This is copied and simplified from FocusFinder's source. There may be some code that doesn't look
  * quite right in Kotlin as it was copy/pasted with auto-translation.
@@ -53,17 +64,15 @@
             get() = FocusFinderThreadLocal.get()!!
     }
 
-    private val focusedRect: Rect = Rect()
+    private val cachedFocusedRect = Rect()
+    private val bestCandidateRect = Rect()
+    private val otherRect = Rect()
 
-    private val userSpecifiedFocusComparator =
-        UserSpecifiedFocusComparator({ r, v ->
-            if (isValidId(v.nextFocusForwardId)) v.findUserSetNextFocus(r, FOCUS_FORWARD) else null
-        })
+    private val userSpecifiedFocusComparator = UserSpecifiedFocusComparator { r, v ->
+        if (isValidId(v.nextFocusForwardId)) v.findUserSetNextFocus(r, FOCUS_FORWARD) else null
+    }
 
-    private val tmpList = MutableObjectList<View>()
-
-    // enforce thread local access
-    private fun FocusFinder() {}
+    private val tmpList = ArrayList<View>()
 
     /**
      * Find the next view to take focus in root's descendants, starting from the view that currently
@@ -74,7 +83,7 @@
      * @param direction Direction to look.
      * @return The next focusable view, or null if none exists.
      */
-    fun findNextFocus1d(root: ViewGroup, focused: View, direction: Int): View? {
+    fun findNextFocus(root: ViewGroup, focused: View, direction: Int): View? {
         val effectiveRoot = getEffectiveRoot(root, focused)
         var next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction)
         if (next != null) {
@@ -85,7 +94,7 @@
             focusables.clear()
             effectiveRoot.addFocusableViews(focusables, direction)
             if (!focusables.isEmpty()) {
-                next = findNextFocus(effectiveRoot, focused, direction, focusables)
+                next = findNextFocus(effectiveRoot, focused, null, direction, focusables)
             }
         } finally {
             focusables.clear()
@@ -94,6 +103,20 @@
     }
 
     /**
+     * Find the next view to take focus in root's descendants, searching from a particular rectangle
+     * in root's coordinates.
+     *
+     * @param root Contains focusedRect. Cannot be null.
+     * @param focusedRect The starting point of the search.
+     * @param direction Direction to look.
+     * @return The next focusable view, or null if none exists.
+     */
+    fun findNextFocusFromRect(root: ViewGroup, focusedRect: Rect, direction: Int): View? {
+        cachedFocusedRect.set(focusedRect)
+        return findNextFocus(root, cachedFocusedRect, direction)
+    }
+
+    /**
      * Returns the "effective" root of a view. The "effective" root is the closest ancestor
      * within-which focus should cycle.
      *
@@ -154,31 +177,79 @@
         return null
     }
 
+    private fun findNextFocus(root: ViewGroup, focusedRect: Rect?, direction: Int): View? {
+        val effectiveRoot = getEffectiveRoot(root, null)
+        val focusables = tmpList
+        try {
+            focusables.clear()
+            effectiveRoot.addFocusableViews(focusables, direction)
+            if (!focusables.isEmpty()) {
+                return findNextFocus(effectiveRoot, null, focusedRect, direction, focusables)
+            }
+            return null
+        } finally {
+            focusables.clear()
+        }
+    }
+
     private fun findNextFocus(
         root: ViewGroup,
-        focused: View,
+        focused: View?,
+        focusedRect: Rect?,
         direction: Int,
-        focusables: MutableObjectList<View>
+        focusables: ArrayList<View>
     ): View? {
-        val focusedRect = focusedRect
-        // fill in interesting rect from focused
-        focused.getFocusedRect(focusedRect)
-        root.offsetDescendantRectToMyCoords(focused, focusedRect)
+        val rect = cachedFocusedRect
+        if (focused != null) {
+            focused.getFocusedRect(rect)
+            root.offsetDescendantRectToMyCoords(focused, rect)
+        } else if (focusedRect != null) {
+            rect.set(focusedRect)
+        } else {
+            // make up a rect at top left or bottom right of root
+            when (direction) {
+                FOCUS_RIGHT,
+                FOCUS_DOWN -> setFocusTopLeft(root, rect)
+                FOCUS_FORWARD ->
+                    if (root.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+                        setFocusBottomRight(root, rect)
+                    } else {
+                        setFocusTopLeft(root, rect)
+                    }
+                FOCUS_LEFT,
+                FOCUS_UP -> setFocusBottomRight(root, rect)
+                FOCUS_BACKWARD ->
+                    if (root.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+                        setFocusTopLeft(root, rect)
+                    } else {
+                        setFocusBottomRight(root, rect)
+                    }
+            }
+        }
 
-        return findNextFocusInRelativeDirection(focusables, root, focused, direction)
+        return when (direction) {
+            FOCUS_FORWARD,
+            FOCUS_BACKWARD -> findNextFocusInRelativeDirection(focusables, root, focused, direction)
+            FOCUS_UP,
+            FOCUS_DOWN,
+            FOCUS_LEFT,
+            FOCUS_RIGHT ->
+                findNextFocusInAbsoluteDirection(root, focused, rect, focusables, direction)
+            else -> throw IllegalArgumentException("Unknown direction: $direction")
+        }
     }
 
     @SuppressLint("AsCollectionCall")
     private fun findNextFocusInRelativeDirection(
-        focusables: MutableObjectList<View>,
-        root: ViewGroup?,
-        focused: View,
+        focusables: ArrayList<View>,
+        root: ViewGroup,
+        focused: View?,
         direction: Int
     ): View? {
         try {
             // Note: This sort is stable.
-            userSpecifiedFocusComparator.setFocusables(focusables, root!!)
-            Collections.sort(focusables.asMutableList(), userSpecifiedFocusComparator)
+            userSpecifiedFocusComparator.setFocusables(focusables, root)
+            Collections.sort(focusables, userSpecifiedFocusComparator)
         } finally {
             userSpecifiedFocusComparator.recycle()
         }
@@ -188,36 +259,90 @@
             return null
         }
         var next: View? = null
-        val looped = BooleanArray(1)
         when (direction) {
-            FOCUS_FORWARD -> next = getNextFocusable(focused, focusables, count, looped)
-            FOCUS_BACKWARD -> next = getPreviousFocusable(focused, focusables, count, looped)
+            FOCUS_FORWARD -> next = getNextFocusable(focused, focusables, count)
+            FOCUS_BACKWARD -> next = getPreviousFocusable(focused, focusables, count)
+            FOCUS_LEFT,
+            FOCUS_RIGHT,
+            FOCUS_UP,
+            FOCUS_DOWN ->
+                next =
+                    findNextFocusInAbsoluteDirection(
+                        root,
+                        focused,
+                        cachedFocusedRect,
+                        focusables,
+                        direction
+                    )
         }
         return next ?: focusables[count - 1]
     }
 
-    private fun getNextFocusable(
-        focused: View,
-        focusables: ObjectList<View>,
-        count: Int,
-        outLooped: BooleanArray
+    private fun setFocusBottomRight(root: ViewGroup, focusedRect: Rect) {
+        val rootBottom = root.scrollY + root.height
+        val rootRight = root.scrollX + root.width
+        focusedRect.set(rootRight, rootBottom, rootRight, rootBottom)
+    }
+
+    private fun setFocusTopLeft(root: ViewGroup, focusedRect: Rect) {
+        val rootTop = root.scrollY
+        val rootLeft = root.scrollX
+        focusedRect.set(rootLeft, rootTop, rootLeft, rootTop)
+    }
+
+    private fun findNextFocusInAbsoluteDirection(
+        root: ViewGroup,
+        focused: View?,
+        focusedRect: Rect,
+        focusables: ArrayList<View>,
+        direction: Int
     ): View? {
+        bestCandidateRect.set(focusedRect)
+        when (direction) {
+            FOCUS_LEFT -> bestCandidateRect.offset(focusedRect.width() + 1, 0)
+            FOCUS_RIGHT -> bestCandidateRect.offset(-focusedRect.width() - 1, 0)
+            FOCUS_UP -> bestCandidateRect.offset(0, focusedRect.height() + 1)
+            FOCUS_DOWN -> bestCandidateRect.offset(0, -focusedRect.height() - 1)
+        }
+
+        var closest: View? = null
+        focusables.fastForEach { focusable ->
+            if (focusable != focused && focusable != root) {
+                focusable.getFocusedRect(otherRect)
+                root.offsetDescendantRectToMyCoords(focusable, otherRect)
+                if (
+                    isBetterCandidate(
+                        otherRect.toComposeRect(),
+                        bestCandidateRect.toComposeRect(),
+                        focusedRect.toComposeRect(),
+                        toFocusDirection(direction) ?: FocusDirection.Next
+                    )
+                ) {
+                    bestCandidateRect.set(otherRect)
+                    closest = focusable
+                }
+            }
+        }
+        return closest
+    }
+
+    private fun getNextFocusable(focused: View?, focusables: ArrayList<View>, count: Int): View? {
         if (count < 2) {
             return null
         }
-        val position = focusables.lastIndexOf(focused)
-        if (position >= 0 && position + 1 < count) {
-            return focusables[position + 1]
+        if (focused != null) {
+            val position = focusables.lastIndexOf(focused)
+            if (position >= 0 && position + 1 < count) {
+                return focusables[position + 1]
+            }
         }
-        outLooped[0] = true
         return focusables[0]
     }
 
     private fun getPreviousFocusable(
         focused: View?,
-        focusables: ObjectList<View>,
-        count: Int,
-        outLooped: BooleanArray
+        focusables: ArrayList<View>,
+        count: Int
     ): View? {
         if (count < 2) {
             return null
@@ -228,7 +353,6 @@
                 return focusables[position - 1]
             }
         }
-        outLooped[0] = true
         return focusables[count - 1]
     }
 
@@ -260,12 +384,11 @@
             nextFoci.clear()
         }
 
-        fun setFocusables(focusables: ObjectList<View>, root: View) {
+        fun setFocusables(focusables: ArrayList<View>, root: View) {
             this.root = root
-            focusables.forEachIndexed { index, view -> originalOrdinal[view] = index }
+            focusables.fastForEachIndexed { index, view -> originalOrdinal[view] = index }
 
-            for (i in focusables.indices.reversed()) {
-                val view = focusables[i]
+            focusables.fastForEachReversed { view ->
                 val next = mNextFocusGetter.get(root, view)
                 if (next != null && originalOrdinal.containsKey(next)) {
                     nextFoci[view] = next
@@ -273,8 +396,7 @@
                 }
             }
 
-            for (i in focusables.indices.reversed()) {
-                val view = focusables[i]
+            focusables.fastForEachReversed { view ->
                 val next = nextFoci[view]
                 if (next != null && !isConnectedTo.contains(view)) {
                     setHeadOfChain(view)
@@ -431,32 +553,135 @@
     return null
 }
 
-private fun View.addFocusableViews(views: MutableObjectList<View>, direction: Int) {
-    addFocusableViews(views, direction, isInTouchMode)
+/**
+ * On older platforms, addFocusableViews() has a different implementation that causes problems with
+ * focus order.
+ */
+private fun View.addFocusableViews(views: ArrayList<View>, direction: Int) {
+    if (Build.VERSION.SDK_INT < 26) {
+        addFocusableViews(views, isInTouchMode)
+    } else {
+        val focusableMode = if (isInTouchMode) FOCUSABLES_TOUCH_MODE else FOCUSABLES_ALL
+        addFocusables(views, direction, focusableMode)
+    }
 }
 
 /**
  * Older versions of View don't add focusable Views in order. This is a corrected version that adds
  * them in the right order.
  */
-private fun View.addFocusableViews(
-    views: MutableObjectList<View>,
-    direction: Int,
-    inTouchMode: Boolean
-) {
-    if (
+@OptIn(ExperimentalStdlibApi::class)
+private fun View.addFocusableViews(views: ArrayList<View>, inTouchMode: Boolean) {
+    val addToViews =
         isVisible &&
             isFocusable &&
             isEnabled &&
             width > 0 &&
             height > 0 &&
             (!inTouchMode || isFocusableInTouchMode)
-    ) {
+
+    if (this is ViewGroup) {
+        val viewCountBefore = views.size
+        val before = descendantFocusability == ViewGroup.FOCUS_BEFORE_DESCENDANTS
+        if (addToViews && before) {
+            views += this
+        }
+        if (descendantFocusability != ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
+            val children = Array<View>(childCount) { index -> getChildAt(index) }
+            FocusSorter.sort(children, this, layoutDirection == View.LAYOUT_DIRECTION_RTL)
+            children.forEach { it.addFocusableViews(views, inTouchMode) }
+        }
+
+        // When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
+        // there aren't any focusable descendants.  this is
+        // to avoid the focus search finding layouts when a more precise search
+        // among the focusable children would be more interesting.
+        if (addToViews && !before && viewCountBefore == views.size) {
+            views += this
+        }
+    } else if (addToViews) {
         views += this
     }
-    if (this is ViewGroup) {
-        for (i in 0 until childCount) {
-            getChildAt(i).addFocusableViews(views, direction, inTouchMode)
+}
+
+/** Copy of FocusSorter from FocusFinder.java in the platform. */
+private object FocusSorter {
+    val rectPool = mutableObjectListOf<Rect>()
+    var lastPoolIndex = 0
+    var rtlMult = 1
+    val rectByView = mutableScatterMapOf<View, Rect>()
+    val topsComparator =
+        Comparator<View> { first, second ->
+            if (first === second) {
+                0
+            } else {
+                val firstRect = rectByView[first]!!
+                val secondRect = rectByView[second]!!
+                val result = firstRect.top - secondRect.top
+                if (result == 0) {
+                    firstRect.bottom - secondRect.bottom
+                } else {
+                    result
+                }
+            }
         }
+    val sidesComparator =
+        Comparator<View> { first, second ->
+            if (first === second) {
+                0
+            } else {
+                val firstRect = rectByView[first]!!
+                val secondRect = rectByView[second]!!
+
+                val result = firstRect.left - secondRect.left
+                if (result == 0) {
+                    (firstRect.right - secondRect.right) * rtlMult
+                } else {
+                    result * rtlMult
+                }
+            }
+        }
+
+    fun sort(views: Array<View>, root: ViewGroup, isRtl: Boolean) {
+        val count = views.size
+        if (count < 2) {
+            return
+        }
+        repeat(count - rectPool.size) { rectPool += Rect() }
+
+        views.forEach { view ->
+            val next = rectPool[lastPoolIndex++]
+            view.getDrawingRect(next)
+            root.offsetDescendantRectToMyCoords(view, next)
+            rectByView[view] = next
+        }
+
+        // sort top-to-bottom
+        views.sortWith(topsComparator)
+        var sweepBottom = rectByView[views[0]]!!.bottom
+        var rowStart = 0
+        rtlMult = if (isRtl) -1 else 1
+        for (sweepIdx in 0 until count) {
+            val currRect = rectByView[views[sweepIdx]]!!
+            if (currRect.top >= sweepBottom) {
+                // Next view is on a new row, sort the row we've just finished left-to-right.
+                if ((sweepIdx - rowStart) > 1) {
+                    views.sortWith(sidesComparator, rowStart, sweepIdx)
+                }
+                sweepBottom = currRect.bottom
+                rowStart = sweepIdx
+            } else {
+                // Next view vertically overlaps, we need to extend our "row height"
+                sweepBottom = maxOf(sweepBottom, currRect.bottom)
+            }
+        }
+
+        // Sort whatever's left (final row) left-to-right
+        if ((count - rowStart) > 1) {
+            views.sortWith(sidesComparator, rowStart, count)
+        }
+
+        lastPoolIndex = 0
+        rectByView.clear()
     }
 }
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt
index fb323fe2..b0ed5ca 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/spatial/ThrottledCallbacksTest.kt
@@ -18,8 +18,8 @@
 
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatableNode.RegistrationHandle
 import androidx.compose.ui.node.LayoutNode
-import kotlinx.coroutines.DisposableHandle
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -60,21 +60,21 @@
         assertEquals(1, b)
         assertEquals(1, c)
 
-        hb.dispose()
+        hb.unregister()
         fire(1)
 
         assertEquals(2, a)
         assertEquals(1, b)
         assertEquals(2, c)
 
-        ha.dispose()
+        ha.unregister()
         fire(1)
 
         assertEquals(2, a)
         assertEquals(1, b)
         assertEquals(3, c)
 
-        hc.dispose()
+        hc.unregister()
         fire(1)
 
         assertEquals(2, a)
@@ -224,7 +224,7 @@
         throttleMs: Long,
         debounceMs: Long,
         callback: (RelativeLayoutBounds) -> Unit
-    ): DisposableHandle {
+    ): RegistrationHandle {
         return registerOnRectChanged(id, throttleMs, debounceMs, fakeNode(), callback)
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListener.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListener.kt
index e65c8c9..da400d8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListener.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnGlobalLayoutListener.kt
@@ -17,10 +17,10 @@
 package androidx.compose.ui.layout
 
 import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatableNode.RegistrationHandle
 import androidx.compose.ui.node.requireLayoutNode
 import androidx.compose.ui.node.requireOwner
 import androidx.compose.ui.spatial.RelativeLayoutBounds
-import kotlinx.coroutines.DisposableHandle
 
 /**
  * Registers a [callback] to be executed with the position of this modifier node relative to the
@@ -57,7 +57,7 @@
     throttleMillis: Long,
     debounceMillis: Long,
     callback: (RelativeLayoutBounds) -> Unit
-): DisposableHandle {
+): RegistrationHandle {
     val layoutNode = requireLayoutNode()
     val id = layoutNode.semanticsId
     val rectManager = layoutNode.requireOwner().rectManager
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnLayoutRectChangedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnLayoutRectChangedModifier.kt
index 37a7f0f..39ea851 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnLayoutRectChangedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnLayoutRectChangedModifier.kt
@@ -19,12 +19,12 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatableNode.RegistrationHandle
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.requireLayoutNode
 import androidx.compose.ui.node.requireOwner
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.spatial.RelativeLayoutBounds
-import kotlinx.coroutines.DisposableHandle
 
 /**
  * Invokes [callback] with the position of this layout node relative to the coordinate system of the
@@ -86,10 +86,10 @@
     var debounceMillis: Long,
     var callback: (RelativeLayoutBounds) -> Unit,
 ) : Modifier.Node() {
-    var handle: DisposableHandle? = null
+    var handle: RegistrationHandle? = null
 
     fun disposeAndRegister() {
-        handle?.dispose()
+        handle?.unregister()
         handle = registerOnLayoutRectChanged(throttleMillis, debounceMillis, callback)
     }
 
@@ -98,7 +98,7 @@
     }
 
     override fun onDetach() {
-        handle?.dispose()
+        handle?.unregister()
     }
 }
 
@@ -129,7 +129,7 @@
     throttleMillis: Long,
     debounceMillis: Long,
     callback: (RelativeLayoutBounds) -> Unit,
-): DisposableHandle {
+): RegistrationHandle {
     val layoutNode = requireLayoutNode()
     val id = layoutNode.semanticsId
     val rectManager = layoutNode.requireOwner().rectManager
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
index 8b45c3f..521ad751 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
@@ -64,6 +64,10 @@
      * can be retrieved inside a node by using [androidx.compose.ui.node.requireLayoutDirection].
      */
     fun onLayoutDirectionChange() {}
+
+    fun interface RegistrationHandle {
+        fun unregister()
+    }
 }
 
 internal val DelegatableNode.isDelegationRoot: Boolean
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
index a2a9f55..4b59513 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
@@ -47,7 +47,7 @@
     // Created and used only when extraAssertions == true
     private var mapOfOriginalDepth: MutableObjectIntMap<LayoutNode>? = null
 
-    private val set = TreeSet(DepthComparator)
+    private val set = SortedSet(DepthComparator)
 
     fun contains(node: LayoutNode): Boolean {
         val contains = set.contains(node)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/SortedSet.kt
similarity index 92%
rename from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt
rename to compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/SortedSet.kt
index 1f106db..defb19e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/SortedSet.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.ui.node
 
-internal expect class TreeSet<E>(comparator: Comparator<in E>) {
+internal expect class SortedSet<E>(comparator: Comparator<in E>) {
     fun add(element: E): Boolean
 
     fun remove(element: E): Boolean
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt
index 557e7ae..dd967a0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.isIdentity
 import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatableNode.RegistrationHandle
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.NodeCoordinator
 import androidx.compose.ui.postDelayed
@@ -38,7 +39,6 @@
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.util.trace
 import kotlin.math.max
-import kotlinx.coroutines.DisposableHandle
 
 internal class RectManager(
     /** [LayoutNode.semanticsId] to [LayoutNode] mapping, maintained by Owner. */
@@ -165,7 +165,7 @@
         debounceMillis: Long,
         node: DelegatableNode,
         callback: (RelativeLayoutBounds) -> Unit
-    ): DisposableHandle {
+    ): RegistrationHandle {
         return throttledCallbacks.registerOnRectChanged(
             id,
             throttleMillis,
@@ -181,7 +181,7 @@
         debounceMillis: Long,
         node: DelegatableNode,
         callback: (RelativeLayoutBounds) -> Unit
-    ): DisposableHandle {
+    ): RegistrationHandle {
         return throttledCallbacks.registerOnGlobalChange(
             id = id,
             throttleMillis = throttleMillis,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt
index 54e02c3..107b104 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/ThrottledCallbacks.kt
@@ -20,13 +20,13 @@
 import androidx.collection.mutableIntObjectMapOf
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatableNode.RegistrationHandle
 import androidx.compose.ui.node.Nodes
 import androidx.compose.ui.node.requireCoordinator
 import androidx.compose.ui.node.requireLayoutNode
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.round
 import kotlin.math.min
-import kotlinx.coroutines.DisposableHandle
 
 internal class ThrottledCallbacks {
 
@@ -41,7 +41,7 @@
         val debounceMillis: Long,
         val node: DelegatableNode,
         val callback: (RelativeLayoutBounds) -> Unit,
-    ) : DisposableHandle {
+    ) : RegistrationHandle {
 
         var next: Entry? = null
 
@@ -50,7 +50,7 @@
         var lastInvokeMillis: Long = -throttleMillis
         var lastUninvokedFireMillis: Long = -1
 
-        override fun dispose() {
+        override fun unregister() {
             val result = rectChangedMap.multiRemove(id, this)
             if (!result) removeFromGlobalEntries(this)
         }
@@ -123,7 +123,7 @@
         debounceMillis: Long,
         node: DelegatableNode,
         callback: (RelativeLayoutBounds) -> Unit,
-    ): DisposableHandle {
+    ): RegistrationHandle {
         // If zero is set for debounce, we use throttle in its place. This guarantees that
         // consumers will get the value where the node "settled".
         val debounceToUse = if (debounceMillis == 0L) throttleMillis else debounceMillis
@@ -147,7 +147,7 @@
         debounceMillis: Long,
         node: DelegatableNode,
         callback: (RelativeLayoutBounds) -> Unit,
-    ): DisposableHandle {
+    ): RegistrationHandle {
         // If zero is set for debounce, we use throttle in its place. This guarantees that
         // consumers will get the value where the node "settled".
         val debounceToUse = if (debounceMillis == 0L) throttleMillis else debounceMillis
diff --git a/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.commonStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.commonStubs.kt
index 9e6134f..3ad311e 100644
--- a/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.commonStubs.kt
+++ b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.commonStubs.kt
@@ -18,7 +18,7 @@
 
 import androidx.compose.ui.implementedInJetBrainsFork
 
-internal actual class TreeSet<E> actual constructor(comparator: Comparator<in E>) {
+internal actual class SortedSet<E> actual constructor(comparator: Comparator<in E>) {
     actual fun add(element: E): Boolean = implementedInJetBrainsFork()
 
     actual fun remove(element: E): Boolean = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/node/JvmTreeSet.jvm.kt b/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/node/JvmTreeSet.jvm.kt
index 610b246..4d747eb 100644
--- a/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/node/JvmTreeSet.jvm.kt
+++ b/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/node/JvmTreeSet.jvm.kt
@@ -16,5 +16,5 @@
 
 package androidx.compose.ui.node
 
-internal actual class TreeSet<E> actual constructor(comparator: Comparator<in E>) :
+internal actual class SortedSet<E> actual constructor(comparator: Comparator<in E>) :
     java.util.TreeSet<E>(comparator)
diff --git a/navigation3/navigation3/samples/build.gradle b/navigation3/navigation3/samples/build.gradle
index 6b9985e..d032c75 100644
--- a/navigation3/navigation3/samples/build.gradle
+++ b/navigation3/navigation3/samples/build.gradle
@@ -43,6 +43,8 @@
     implementation("androidx.compose.foundation:foundation-layout:1.7.5")
     implementation("androidx.compose.material:material:1.7.5")
     implementation("androidx.compose.material3:material3:1.3.1")
+    implementation("androidx.compose.material3:material3-window-size-class:1.3.1")
+    implementation("androidx.compose.material3:material3-adaptive-navigation-suite:1.3.1")
     implementation("androidx.compose.runtime:runtime:1.7.5")
     implementation("androidx.compose.ui:ui:1.7.5")
     implementation("androidx.lifecycle:lifecycle-viewmodel:2.8.7")
diff --git a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/CommonUiNavDisplaySample.kt b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/CommonUiNavDisplaySample.kt
new file mode 100644
index 0000000..49e051f
--- /dev/null
+++ b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/CommonUiNavDisplaySample.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2025 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.navigation3.samples
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.material3.Icon
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.navigation3.NavBackStackProvider
+import androidx.navigation3.NavEntry
+import androidx.navigation3.NavLocalProvider
+import androidx.navigation3.samples.CommonUiNavDisplay.DEFAULT_TRANSITION_DURATION_MILLISECOND
+import androidx.navigation3.samples.CommonUiNavDisplay.ENTER_TRANSITION_KEY
+import androidx.navigation3.samples.CommonUiNavDisplay.EXIT_TRANSITION_KEY
+import androidx.navigation3.samples.CommonUiNavDisplay.NAV_UI_LAYOUT_POLICY
+import androidx.navigation3.samples.CommonUiNavDisplay.POP_ENTER_TRANSITION_KEY
+import androidx.navigation3.samples.CommonUiNavDisplay.POP_EXIT_TRANSITION_KEY
+
+/** Object that indicates the features that can be handled by the [CommonUiNavDisplay] */
+object CommonUiNavDisplay {
+    internal const val ENTER_TRANSITION_KEY = "enterTransition"
+    internal const val EXIT_TRANSITION_KEY = "exitTransition"
+    internal const val POP_ENTER_TRANSITION_KEY = "popEnterTransition"
+    internal const val POP_EXIT_TRANSITION_KEY = "popExitTransition"
+    internal const val NAV_UI_LAYOUT_POLICY = "navUiLayoutPolicy"
+    const val DEFAULT_TRANSITION_DURATION_MILLISECOND = 700
+
+    /**
+     * Function to be called on the [NavEntry.featureMap] to notify the [CommonUiNavDisplay] that
+     * the content should be animated using the provided transitions.
+     */
+    fun transition(enter: EnterTransition?, exit: ExitTransition?): Map<String, Any> =
+        if (enter == null || exit == null) emptyMap()
+        else mapOf(ENTER_TRANSITION_KEY to enter, EXIT_TRANSITION_KEY to exit)
+
+    /**
+     * Function to be called on the [NavEntry.featureMap] to notify the [CommonUiNavDisplay] that,
+     * when popping from backstack, the content should be animated using the provided transitions.
+     */
+    fun popTransition(enter: EnterTransition?, exit: ExitTransition?): Map<String, Any> =
+        if (enter == null || exit == null) emptyMap()
+        else mapOf(POP_ENTER_TRANSITION_KEY to enter, POP_EXIT_TRANSITION_KEY to exit)
+
+    /**
+     * Function to be called on the [NavEntry.featureMap] to notify the [CommonUiNavDisplay] that
+     * updates the [NavUiLayoutPolicy].
+     */
+    fun layoutPolicy(value: NavUiLayoutPolicy = NavUiLayoutPolicy.Default): Map<String, Any> =
+        mapOf(NAV_UI_LAYOUT_POLICY to value)
+}
+
+@Composable
+fun <T : Any> CommonUiNavDisplay(
+    backstack: List<T>,
+    topLevelRoutes: List<TopLevelRoute>,
+    onItemClick: (TopLevelRoute) -> Unit,
+    modifier: Modifier = Modifier,
+    localProviders: List<NavLocalProvider> = emptyList(),
+    contentAlignment: Alignment = Alignment.TopStart,
+    sizeTransform: SizeTransform? = null,
+    enterTransition: EnterTransition =
+        fadeIn(
+            animationSpec =
+                tween(
+                    DEFAULT_TRANSITION_DURATION_MILLISECOND,
+                )
+        ),
+    exitTransition: ExitTransition =
+        fadeOut(
+            animationSpec =
+                tween(
+                    DEFAULT_TRANSITION_DURATION_MILLISECOND,
+                )
+        ),
+    popEnterTransition: EnterTransition = enterTransition,
+    popExitTransition: ExitTransition = exitTransition,
+    onBack: () -> Unit = { if (backstack is MutableList) backstack.removeAt(backstack.size - 1) },
+    entryProvider: (key: T) -> NavEntry<out T>
+) {
+    BackHandler(backstack.size > 1, onBack)
+    NavBackStackProvider(backstack, localProviders, entryProvider) { entries ->
+        // Make a copy shallow copy so that transition.currentState and transition.targetState are
+        // different backstack instances. This ensures currentState reflects the old backstack when
+        // the backstack (targetState) is updated.
+        val newStack = backstack.toList()
+        val entry = entries.last()
+        val transition = updateTransition(targetState = newStack, label = newStack.toString())
+        val isPop = isPop(transition.currentState, newStack)
+        // Incoming entry defines transitions, otherwise it uses default transitions from
+        // NavDisplay
+        val finalEnterTransition =
+            if (isPop) {
+                entry.featureMap[POP_ENTER_TRANSITION_KEY] as? EnterTransition ?: popEnterTransition
+            } else {
+                entry.featureMap[ENTER_TRANSITION_KEY] as? EnterTransition ?: enterTransition
+            }
+        val finalExitTransition =
+            if (isPop) {
+                entry.featureMap[POP_EXIT_TRANSITION_KEY] as? ExitTransition ?: popExitTransition
+            } else {
+                entry.featureMap[EXIT_TRANSITION_KEY] as? ExitTransition ?: exitTransition
+            }
+        transition.AnimatedContent(
+            modifier = modifier,
+            transitionSpec = {
+                ContentTransform(
+                    targetContentEnter = finalEnterTransition,
+                    initialContentExit = finalExitTransition,
+                    sizeTransform = sizeTransform
+                )
+            },
+            contentAlignment = contentAlignment,
+            contentKey = { it.last() }
+        ) { innerStack ->
+            val lastKey = innerStack.last()
+            val layoutPolicy =
+                entry.featureMap[NAV_UI_LAYOUT_POLICY] as? NavUiLayoutPolicy
+                    ?: NavUiLayoutPolicy.Default
+            NavigationSuiteScaffold(
+                layoutType = layoutPolicy.toLayoutType(),
+                navigationSuiteItems = {
+                    topLevelRoutes.forEach { topLevelRoute ->
+                        item(
+                            selected = topLevelRoute.route == innerStack.last(),
+                            onClick = { onItemClick(topLevelRoute) },
+                            icon = {
+                                Icon(imageVector = topLevelRoute.icon, contentDescription = null)
+                            }
+                        )
+                    }
+                }
+            ) {
+                entries.findLast { entry -> entry.key == lastKey }?.content?.invoke(lastKey)
+            }
+        }
+    }
+}
+
+data class TopLevelRoute(val route: Any, val icon: ImageVector)
+
+enum class NavUiLayoutPolicy {
+    Default, // Default layout behavior
+    None, // Never show the common UI
+    NoNavBar; // Never show the nav bar
+
+    @Composable
+    fun toLayoutType(): NavigationSuiteType {
+        val defaultLayout =
+            NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(currentWindowAdaptiveInfo())
+        return when (this) {
+            Default -> defaultLayout
+            None -> {
+                NavigationSuiteType.None
+            }
+            NoNavBar -> {
+                if (defaultLayout == NavigationSuiteType.NavigationBar) {
+                    NavigationSuiteType.None
+                } else {
+                    defaultLayout
+                }
+            }
+        }
+    }
+}
+
+private fun <T : Any> isPop(oldBackStack: List<T>, newBackStack: List<T>): Boolean {
+    // entire stack replaced
+    if (oldBackStack.first() != newBackStack.first()) return false
+    // navigated
+    if (newBackStack.size > oldBackStack.size) return false
+    val divergingIndex =
+        newBackStack.indices.firstOrNull { index -> newBackStack[index] != oldBackStack[index] }
+    // if newBackStack never diverged from oldBackStack, then it is a clean subset of the oldStack
+    // and is a pop
+    return divergingIndex == null
+}
diff --git a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/ListDetailNavDisplaySample.kt b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/ListDetailNavDisplaySample.kt
new file mode 100644
index 0000000..c4bcf0b
--- /dev/null
+++ b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/ListDetailNavDisplaySample.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2025 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.navigation3.samples
+
+import androidx.activity.compose.BackHandler
+import androidx.activity.compose.LocalActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
+import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation3.NavBackStackProvider
+import androidx.navigation3.NavEntry
+import androidx.navigation3.NavLocalProvider
+import androidx.navigation3.samples.ListDetailNavDisplay.IS_SUPPORTING_PANE
+
+object ListDetailNavDisplay {
+    internal const val IS_SUPPORTING_PANE = "isSupportingPane"
+
+    fun isSupportingPane(value: Boolean) = mapOf(IS_SUPPORTING_PANE to value)
+}
+
+@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
+@Composable
+fun <T : Any> ListDetailNavDisplay(
+    backstack: List<T>,
+    modifier: Modifier = Modifier,
+    localProviders: List<NavLocalProvider> = emptyList(),
+    onBack: () -> Unit = { if (backstack is MutableList) backstack.removeAt(backstack.size - 1) },
+    windowWidthSizeClass: WindowWidthSizeClass =
+        calculateWindowSizeClass(LocalActivity.current!!).widthSizeClass,
+    entryProvider: (key: T) -> NavEntry<T>
+) {
+    val isSinglePaneLayout = (windowWidthSizeClass == WindowWidthSizeClass.Compact)
+    BackHandler(isSinglePaneLayout && backstack.size > 1, onBack)
+    NavBackStackProvider(backstack, localProviders, entryProvider) { entries ->
+        val lastEntry = entries.last()
+        if (isSinglePaneLayout) {
+            Box(modifier = modifier) { lastEntry.content.invoke(lastEntry.key) }
+        } else {
+            Row {
+                var rightEntry: NavEntry<T>? = null
+                val leftEntry: NavEntry<T>?
+                val isSupportingPane =
+                    lastEntry.featureMap[IS_SUPPORTING_PANE]?.equals(true) ?: false
+                if (isSupportingPane) {
+                    // Display the penultimate entry in the left pane
+                    leftEntry = entries[entries.size - 2]
+                    // Display the last entry in the right pane
+                    rightEntry = lastEntry
+                } else {
+                    // Display the last entry in the left pane
+                    leftEntry = lastEntry
+                }
+                // Left pane
+                Box(modifier = modifier.fillMaxWidth(0.5F)) {
+                    leftEntry.content.invoke(leftEntry.key)
+                }
+                // Right pane
+                Box(modifier = modifier.fillMaxWidth()) {
+                    if (rightEntry == null) {
+                        Text("Please select an item")
+                    } else {
+                        rightEntry.content.invoke(rightEntry.key)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
index 35b1a3b..88515b7 100644
--- a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
+++ b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
@@ -18,6 +18,8 @@
 
 import androidx.activity.compose.BackHandler
 import androidx.annotation.Sampled
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionLayout
 import androidx.compose.animation.slideInHorizontally
 import androidx.compose.animation.slideOutHorizontally
 import androidx.compose.foundation.layout.Box
@@ -88,6 +90,30 @@
     }
 }
 
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Sampled
+@Composable
+fun <T : Any> NavSharedElementSample() {
+    val backStack = rememberMutableStateListOf(CatList)
+    SharedTransitionLayout {
+        SinglePaneNavDisplay(
+            backstack = backStack,
+            onBack = { backStack.removeLast() },
+            entryProvider =
+                entryProvider {
+                    entry<CatList> {
+                        CatList(this@SharedTransitionLayout) { cat ->
+                            backStack.add(CatDetail(cat))
+                        }
+                    }
+                    entry<CatDetail> { args ->
+                        CatDetail(args.cat, this@SharedTransitionLayout) { backStack.removeLast() }
+                    }
+                }
+        )
+    }
+}
+
 @Sampled
 @Composable
 fun <T : Any> CustomBasicDisplay(
diff --git a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt
index 6a5ec92..beffa4f 100644
--- a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt
+++ b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt
@@ -16,8 +16,15 @@
 
 package androidx.navigation3.samples
 
+import androidx.annotation.DrawableRes
+import androidx.compose.animation.ExperimentalSharedTransitionApi
+import androidx.compose.animation.SharedTransitionScope
+import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -39,10 +46,12 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color.Companion.LightGray
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.window.Dialog
+import androidx.navigation3.LocalNavAnimatedContentScope
 import androidx.savedstate.serialization.decodeFromSavedState
 import androidx.savedstate.serialization.encodeToSavedState
 import kotlinx.serialization.InternalSerializationApi
@@ -78,6 +87,13 @@
     }
 }
 
+@Serializable object CatList
+
+@Serializable data class CatDetail(val cat: Cat)
+
+@Serializable
+data class Cat(@DrawableRes val imageId: Int, val name: String, val description: String)
+
 @Composable
 fun Profile(viewModel: ProfileViewModel, navigateTo: (Any) -> Unit, onBack: () -> Unit) {
     Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
@@ -135,6 +151,48 @@
     }
 }
 
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun CatList(sharedScope: SharedTransitionScope, onClick: (cat: Cat) -> Unit) {
+    Column {
+        catList.forEach { cat: Cat ->
+            Row(Modifier.clickable { onClick(cat) }) {
+                with(sharedScope) {
+                    val imageModifier =
+                        Modifier.size(100.dp)
+                            .sharedElement(
+                                sharedScope.rememberSharedContentState(key = cat.imageId),
+                                animatedVisibilityScope = LocalNavAnimatedContentScope.current
+                            )
+                    Image(painterResource(cat.imageId), cat.description, imageModifier)
+                    Text(cat.name)
+                }
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalSharedTransitionApi::class)
+@Composable
+fun CatDetail(cat: Cat, sharedScope: SharedTransitionScope, onBack: () -> Unit) {
+    Column {
+        Box {
+            with(sharedScope) {
+                val imageModifier =
+                    Modifier.size(300.dp)
+                        .sharedElement(
+                            sharedScope.rememberSharedContentState(key = cat.imageId),
+                            animatedVisibilityScope = LocalNavAnimatedContentScope.current
+                        )
+                Image(painterResource(cat.imageId), cat.description, imageModifier)
+            }
+        }
+        Text(cat.name)
+        Text(cat.description)
+        NavigateBackButton(onBack)
+    }
+}
+
 @Composable
 fun NavigateButton(text: String, listener: () -> Unit = {}) {
     Button(
@@ -191,6 +249,13 @@
         "Go For Broke"
     )
 
+private val catList: List<Cat> =
+    listOf(
+        Cat(R.drawable.cat_1, "happy", "cat lying down"),
+        Cat(R.drawable.cat_2, "lucky", "cat playing"),
+        Cat(R.drawable.cat_3, "chocolate cake", "cat upside down"),
+    )
+
 @Composable
 fun <T : Any> rememberMutableStateListOf(vararg elements: T): SnapshotStateList<Any> {
     return rememberSaveable(saver = snapshotStateListSaver(serializableListSaver())) {
diff --git a/navigation3/navigation3/samples/src/main/res/drawable/cat_1.jpg b/navigation3/navigation3/samples/src/main/res/drawable/cat_1.jpg
new file mode 100644
index 0000000..a68eccd
--- /dev/null
+++ b/navigation3/navigation3/samples/src/main/res/drawable/cat_1.jpg
Binary files differ
diff --git a/navigation3/navigation3/samples/src/main/res/drawable/cat_2.jpg b/navigation3/navigation3/samples/src/main/res/drawable/cat_2.jpg
new file mode 100644
index 0000000..801a5e5
--- /dev/null
+++ b/navigation3/navigation3/samples/src/main/res/drawable/cat_2.jpg
Binary files differ
diff --git a/navigation3/navigation3/samples/src/main/res/drawable/cat_3.jpg b/navigation3/navigation3/samples/src/main/res/drawable/cat_3.jpg
new file mode 100644
index 0000000..1f07a49
--- /dev/null
+++ b/navigation3/navigation3/samples/src/main/res/drawable/cat_3.jpg
Binary files differ
diff --git a/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/LocalNavAnimatedContentScope.android.kt b/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/LocalNavAnimatedContentScope.android.kt
index fbc6e74..ed7bf28 100644
--- a/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/LocalNavAnimatedContentScope.android.kt
+++ b/navigation3/navigation3/src/androidMain/kotlin/androidx/navigation3/LocalNavAnimatedContentScope.android.kt
@@ -25,6 +25,8 @@
  *
  * This does not have a default value since the AnimatedContentScope is provided at runtime by
  * AnimatedContent.
+ *
+ * @sample androidx.navigation3.samples.NavSharedElementSample
  */
 public val LocalNavAnimatedContentScope: ProvidableCompositionLocal<AnimatedContentScope> =
     compositionLocalOf<AnimatedContentScope> {
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/FragmentUtils.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/FragmentUtils.kt
new file mode 100644
index 0000000..e50b0b9
--- /dev/null
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/FragmentUtils.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2025 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.pdf
+
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.lifecycle.Lifecycle
+import androidx.pdf.idlingresource.PdfIdlingResource
+import androidx.test.platform.app.InstrumentationRegistry
+
+internal object FragmentUtils {
+
+    private const val TEST_DOCUMENT_FILE = "sample.pdf"
+
+    internal fun scenarioLoadDocument(
+        scenario: FragmentScenario<TestPdfViewerFragmentV2>,
+        filename: String = TEST_DOCUMENT_FILE,
+        nextState: Lifecycle.State,
+        orientation: Int,
+        pdfLoadingIdlingResource: PdfIdlingResource
+    ): FragmentScenario<TestPdfViewerFragmentV2> {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val inputStream = context.assets.open(filename)
+
+        scenario.moveToState(nextState)
+        scenario.onFragment { it.requireActivity().requestedOrientation = orientation }
+
+        // Load the document in the fragment
+        scenario.onFragment { fragment ->
+            fragment.documentUri = TestUtils.saveStream(inputStream, fragment.requireContext())
+            // Increment pdf load idling resource to wait for background task to complete.
+            pdfLoadingIdlingResource.increment()
+        }
+
+        return scenario
+    }
+}
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/SearchInteractionTest.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/SearchInteractionTest.kt
new file mode 100644
index 0000000..a7a5c10
--- /dev/null
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/SearchInteractionTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2025 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.pdf
+
+import android.content.pm.ActivityInfo
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.lifecycle.Lifecycle
+import androidx.pdf.FragmentUtils.scenarioLoadDocument
+import androidx.pdf.view.PdfView
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.IdlingRegistry
+import androidx.test.espresso.action.ViewActions.longClick
+import androidx.test.espresso.action.ViewActions.typeText
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isRoot
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import junit.framework.TestCase.assertNotNull
+import junit.framework.TestCase.assertNull
+import org.hamcrest.CoreMatchers.not
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 35)
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
+internal class SearchInteractionTest {
+
+    private lateinit var scenario: FragmentScenario<TestPdfViewerFragmentV2>
+
+    @Before
+    fun setup() {
+        scenario =
+            launchFragmentInContainer<TestPdfViewerFragmentV2>(
+                themeResId =
+                    com.google.android.material.R.style.Theme_Material3_DayNight_NoActionBar,
+                initialState = Lifecycle.State.INITIALIZED
+            )
+
+        scenario.onFragment { fragment ->
+            // Register idling resource
+            IdlingRegistry.getInstance()
+                .register(fragment.pdfLoadingIdlingResource.countingIdlingResource)
+
+            scenarioLoadDocument(
+                scenario = scenario,
+                nextState = Lifecycle.State.STARTED,
+                orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
+                pdfLoadingIdlingResource = fragment.pdfLoadingIdlingResource
+            )
+        }
+    }
+
+    @After
+    fun cleanup() {
+        scenario.onFragment { fragment ->
+            // Un-register idling resource
+            IdlingRegistry.getInstance()
+                .unregister(fragment.pdfLoadingIdlingResource.countingIdlingResource)
+        }
+        scenario.close()
+    }
+
+    @Test
+    fun test_searchClosed_upon_textSelection() {
+        onView(withId(androidx.pdf.viewer.fragment.R.id.pdfView)).check(matches(isDisplayed()))
+
+        var pdfView: PdfView? = null
+        scenario.onFragment { fragment ->
+            pdfView =
+                fragment.view?.findViewById<PdfView>(androidx.pdf.viewer.fragment.R.id.pdfView)
+            fragment.isTextSearchActive = true
+        }
+
+        onView(withId(R.id.searchQueryBox)).perform(typeText(SEARCH_QUERY))
+        onView(withId(R.id.matchStatusTextView)).check(matches(isDisplayed()))
+        onView(withId(R.id.matchStatusTextView)).check(matches(withText("1 / 24")))
+
+        // Start selection on PdfView
+        onView(isRoot()).perform(longClick())
+        assertNotNull(pdfView?.currentSelection)
+
+        // assert search is not displayed
+        onView(withId(androidx.pdf.viewer.fragment.R.id.pdfSearchView))
+            .check(matches(not(isDisplayed())))
+        scenario.onFragment { fragment -> assertFalse(fragment.isTextSearchActive) }
+    }
+
+    @Test
+    fun test_selection_cleared_upon_search() {
+        onView(withId(androidx.pdf.viewer.fragment.R.id.pdfView)).check(matches(isDisplayed()))
+
+        var pdfView: PdfView? = null
+        scenario.onFragment { fragment ->
+            pdfView =
+                fragment.view?.findViewById<PdfView>(androidx.pdf.viewer.fragment.R.id.pdfView)
+        }
+
+        // Start selection on PdfView
+        onView(isRoot()).perform(longClick())
+        assertNotNull(pdfView?.currentSelection)
+
+        // Enable search on document
+        scenario.onFragment { fragment -> fragment.isTextSearchActive = true }
+
+        // assert selection cleared on pdfView
+        assertNull(pdfView?.currentSelection)
+
+        // Check if search is functional
+        onView(withId(R.id.searchQueryBox)).perform(typeText(SEARCH_QUERY))
+        onView(withId(R.id.matchStatusTextView)).check(matches(isDisplayed()))
+        onView(withId(R.id.matchStatusTextView)).check(matches(withText("1 / 24")))
+    }
+
+    companion object {
+        private const val SEARCH_QUERY = "ipsum"
+    }
+}
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/TestPdfViewerFragmentV2.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/TestPdfViewerFragmentV2.kt
new file mode 100644
index 0000000..9b77603
--- /dev/null
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/TestPdfViewerFragmentV2.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025 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.pdf
+
+import androidx.pdf.idlingresource.PdfIdlingResource
+import androidx.pdf.viewer.fragment.PdfViewerFragmentV2
+
+/**
+ * A subclass fragment from [PdfViewerFragmentV2] to include [androidx.test.espresso.IdlingResource]
+ * while loading pdf document.
+ *
+ * TODO(b/386721657) Remove this when PdfViewerFragment is replaced with PdfViewerFragmentV2.
+ */
+internal class TestPdfViewerFragmentV2 : PdfViewerFragmentV2() {
+
+    val pdfLoadingIdlingResource = PdfIdlingResource(PDF_LOAD_RESOURCE_NAME)
+
+    override fun onLoadDocumentSuccess() {
+        pdfLoadingIdlingResource.decrement()
+    }
+
+    override fun onLoadDocumentError(error: Throwable) {
+        pdfLoadingIdlingResource.decrement()
+    }
+
+    companion object {
+        private const val PDF_LOAD_RESOURCE_NAME = "PdfLoad"
+    }
+}
diff --git a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/insets/TranslateInsetsAnimationCallback.kt b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/insets/TranslateInsetsAnimationCallback.kt
index 2077cc6..719f0a3 100644
--- a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/insets/TranslateInsetsAnimationCallback.kt
+++ b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/insets/TranslateInsetsAnimationCallback.kt
@@ -45,7 +45,10 @@
                 insets.getInsets(WindowInsetsCompat.Type.ime()).run {
                     Insets.of(left, top, right, bottom)
                 }
-            translateViewWithKeyboard(keyboardInsets)
+            // Avoid consuming null ime events which are set as zero on configuration changes.
+            if (keyboardInsets.bottom > 0) {
+                translateViewWithKeyboard(keyboardInsets)
+            }
 
             insets
         }
diff --git a/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfViewerFragmentV2.kt b/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfViewerFragmentV2.kt
index 19b75a2..46a33dd 100644
--- a/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfViewerFragmentV2.kt
+++ b/pdf/pdf-viewer-fragment/src/main/kotlin/androidx/pdf/viewer/fragment/PdfViewerFragmentV2.kt
@@ -40,6 +40,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.pdf.view.PdfView
+import androidx.pdf.view.Selection
 import androidx.pdf.view.ToolBoxView
 import androidx.pdf.view.search.PdfSearchView
 import androidx.pdf.viewer.PdfPasswordDialog
@@ -49,6 +50,7 @@
 import androidx.pdf.viewer.fragment.model.PdfFragmentUiState.DocumentLoaded
 import androidx.pdf.viewer.fragment.model.PdfFragmentUiState.Loading
 import androidx.pdf.viewer.fragment.model.PdfFragmentUiState.PasswordRequested
+import androidx.pdf.viewer.fragment.model.SearchViewUiState
 import androidx.pdf.viewer.fragment.search.PdfSearchViewManager
 import androidx.pdf.viewer.fragment.util.getCenter
 import androidx.pdf.viewer.fragment.view.PdfViewManager
@@ -207,6 +209,8 @@
             )
         pdfSearchViewManager = PdfSearchViewManager(pdfSearchView)
 
+        setupPdfViewListeners()
+
         onPdfSearchViewCreated(pdfSearchView)
 
         collectFlowOnLifecycleScope { collectFragmentUiScreenState() }
@@ -238,6 +242,23 @@
         }
     }
 
+    private fun setupPdfViewListeners() {
+        /**
+         * Closes any active search session if the user selects anything in the PdfView. This
+         * improves the user experience by allowing the focus to shift to the intended content.
+         */
+        pdfView.addOnSelectionChangedListener(
+            object : PdfView.OnSelectionChangedListener {
+                override fun onSelectionChanged(
+                    previousSelection: Selection?,
+                    newSelection: Selection?
+                ) {
+                    newSelection?.let { isTextSearchActive = false }
+                }
+            }
+        )
+    }
+
     private fun setupSearchViewListeners(pdfSearchView: PdfSearchView) {
         with(pdfSearchView) {
             searchQueryBox.addTextChangedListener(searchQueryTextWatcher)
@@ -274,6 +295,9 @@
         searchStateCollector = collectFlowOnLifecycleScope {
             documentViewModel.searchViewUiState.collect { uiState ->
                 pdfSearchViewManager.setState(uiState)
+
+                /** Clear selection when we start a search session. */
+                if (uiState !is SearchViewUiState.Closed) pdfView.clearSelection()
             }
         }
 
diff --git a/pdf/pdf-viewer/build.gradle b/pdf/pdf-viewer/build.gradle
index 9e1499f..2efa9ad 100644
--- a/pdf/pdf-viewer/build.gradle
+++ b/pdf/pdf-viewer/build.gradle
@@ -53,7 +53,6 @@
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.mockitoCore)
     androidTestImplementation(libs.dexmakerMockito)
     androidTestImplementation(libs.truth)
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculatorTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculatorTest.kt
index 46b5957..673f90f 100644
--- a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculatorTest.kt
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollCalculatorTest.kt
@@ -17,6 +17,8 @@
 package androidx.pdf.view.fastscroll
 
 import android.content.Context
+import android.content.res.Resources
+import android.util.DisplayMetrics
 import androidx.pdf.R
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -25,6 +27,8 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -72,7 +76,15 @@
 
     @Test
     fun computeThumbPosition() = runTest {
-        val calculator = FastScrollCalculator(context)
+        val mockContext = mock<Context>()
+        val mockResources = mock<Resources>()
+
+        val displayMetrics = DisplayMetrics()
+        displayMetrics.density = 2f
+        whenever(mockContext.resources).thenReturn(mockResources)
+        whenever(mockResources.displayMetrics).thenReturn(displayMetrics)
+
+        val calculator = FastScrollCalculator(mockContext)
 
         val fastScrollY =
             calculator.computeThumbPosition(
@@ -83,7 +95,7 @@
                 estimatedFullHeight = 1000
             )
 
-        val expectedScrollValue = 107
+        val expectedScrollValue = 100
         assertEquals(expectedScrollValue, fastScrollY)
     }
 }
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawerTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawerTest.kt
index 79fda6e..19be618 100644
--- a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawerTest.kt
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/fastscroll/FastScrollDrawerTest.kt
@@ -96,7 +96,7 @@
         val expectedLeftRange = Range(600, 700)
         val expectedTopRange = Range(100, 200)
         val expectedRightRange = Range(700, 800)
-        val expectedBottomRange = Range(200, 300)
+        val expectedBottomRange = Range(100, 300)
         assertTrue(expectedLeftRange.contains(leftCaptor.value))
         assertTrue(expectedTopRange.contains(topCaptor.value))
         assertTrue(expectedRightRange.contains(rightCaptor.value))
@@ -106,8 +106,16 @@
         verify(spyCanvas).drawText(textCaptor.capture(), anyFloat(), anyFloat(), any())
 
         // Hyphens are being interpreted in unicode rather than ascii which is failing the assertion
-        // hence forcing ascii conversion
-        val expectedLabelValue = "2-6 / 10".replace("\u2014", "-")
-        assertEquals(expectedLabelValue, textCaptor.value)
+        // hence using indices to find characters
+        val pageIndicatorLabels = textCaptor.value.toString().trim().split('/')
+        val pageRange = pageIndicatorLabels[0].trim()
+        val totalPages = pageIndicatorLabels[1].trim()
+
+        val expectedLowerPageRange = 2
+        val expectedUpperPageRange = 6
+        val expectedTotalPages = 10
+        assertEquals(expectedLowerPageRange, pageRange[0].toString().toInt())
+        assertEquals(expectedUpperPageRange, pageRange[2].toString().toInt())
+        assertEquals(expectedTotalPages, totalPages.toString().toInt())
     }
 }
diff --git a/privacysandbox/ui/ui-client/api/current.txt b/privacysandbox/ui/ui-client/api/current.txt
index 94a15f0..a5c079b 100644
--- a/privacysandbox/ui/ui-client/api/current.txt
+++ b/privacysandbox/ui/ui-client/api/current.txt
@@ -6,6 +6,11 @@
     field public static final androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory INSTANCE;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public final class SharedUiAdapterFactory {
+    method @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public androidx.privacysandbox.ui.core.SharedUiAdapter createFromCoreLibInfo(android.os.Bundle coreLibInfo);
+    field public static final androidx.privacysandbox.ui.client.SharedUiAdapterFactory INSTANCE;
+  }
+
 }
 
 package androidx.privacysandbox.ui.client.view {
diff --git a/privacysandbox/ui/ui-client/api/restricted_current.txt b/privacysandbox/ui/ui-client/api/restricted_current.txt
index 94a15f0..a5c079b 100644
--- a/privacysandbox/ui/ui-client/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-client/api/restricted_current.txt
@@ -6,6 +6,11 @@
     field public static final androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory INSTANCE;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public final class SharedUiAdapterFactory {
+    method @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public androidx.privacysandbox.ui.core.SharedUiAdapter createFromCoreLibInfo(android.os.Bundle coreLibInfo);
+    field public static final androidx.privacysandbox.ui.client.SharedUiAdapterFactory INSTANCE;
+  }
+
 }
 
 package androidx.privacysandbox.ui.client.view {
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SharedUiAdapterFactory.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SharedUiAdapterFactory.kt
new file mode 100644
index 0000000..e847a08
--- /dev/null
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SharedUiAdapterFactory.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2025 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.privacysandbox.ui.client
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.privacysandbox.ui.core.ExperimentalFeatures
+import androidx.privacysandbox.ui.core.IRemoteSharedUiSessionClient
+import androidx.privacysandbox.ui.core.IRemoteSharedUiSessionController
+import androidx.privacysandbox.ui.core.ISharedUiAdapter
+import androidx.privacysandbox.ui.core.RemoteCallManager.addBinderDeathListener
+import androidx.privacysandbox.ui.core.RemoteCallManager.closeRemoteSession
+import androidx.privacysandbox.ui.core.RemoteCallManager.tryToCallRemoteObject
+import androidx.privacysandbox.ui.core.SharedUiAdapter
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+import java.util.concurrent.Executor
+
+/**
+ * Provides an implementation of [SharedUiAdapter] created from a supplied Bundle which acts as a
+ * proxy between the host app and the Binder provided by the UI provider.
+ */
+@SuppressLint("NullAnnotationGroup")
+@ExperimentalFeatures.SharedUiPresentationApi
+object SharedUiAdapterFactory {
+
+    private const val TAG = "PrivacySandboxUiLib"
+    // Bundle key is a binary compatibility requirement
+    private const val SHARED_UI_ADAPTER_BINDER = "sharedUiAdapterBinder"
+    private const val TEST_ONLY_USE_REMOTE_ADAPTER = "testOnlyUseRemoteAdapter"
+
+    /**
+     * Creates a [SharedUiAdapter] from a supplied [coreLibInfo] that acts as a proxy between the
+     * host app and the Binder provided by the UI provider.
+     *
+     * @throws IllegalArgumentException if `coreLibInfo` does not contain a Binder corresponding to
+     *   [SharedUiAdapter]
+     */
+    // TODO(b/365553832): add shim support to generate client proxy.
+    @SuppressLint("NullAnnotationGroup")
+    @ExperimentalFeatures.SharedUiPresentationApi
+    fun createFromCoreLibInfo(coreLibInfo: Bundle): SharedUiAdapter {
+        val uiAdapterBinder =
+            requireNotNull(coreLibInfo.getBinder(SHARED_UI_ADAPTER_BINDER)) {
+                "Invalid bundle, missing $SHARED_UI_ADAPTER_BINDER."
+            }
+        val adapterInterface = ISharedUiAdapter.Stub.asInterface(uiAdapterBinder)
+
+        val forceUseRemoteAdapter = coreLibInfo.getBoolean(TEST_ONLY_USE_REMOTE_ADAPTER)
+        val isLocalBinder = uiAdapterBinder.queryLocalInterface(ISharedUiAdapter.DESCRIPTOR) != null
+        val useLocalAdapter = !forceUseRemoteAdapter && isLocalBinder
+        Log.d(TAG, "useLocalAdapter=$useLocalAdapter")
+
+        return if (
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !useLocalAdapter
+        ) {
+            RemoteAdapter(adapterInterface)
+        } else {
+            LocalAdapter(adapterInterface)
+        }
+    }
+
+    /**
+     * [LocalAdapter] communicates with a provider living on same process as the client but on a
+     * different class loader.
+     */
+    @SuppressLint("BanUncheckedReflection") // using reflection on library classes
+    private class LocalAdapter(adapterInterface: ISharedUiAdapter) : SharedUiAdapter {
+        private val uiProviderBinder = adapterInterface.asBinder()
+
+        private val targetSharedSessionClientClass =
+            Class.forName(
+                SharedUiAdapter.SessionClient::class.java.name,
+                /* initialize = */ false,
+                uiProviderBinder.javaClass.classLoader
+            )
+
+        // The adapterInterface provided must have a openSession method on its class.
+        // Since the object itself has been instantiated on a different classloader, we
+        // need reflection to get hold of it.
+        private val openSessionMethod: Method =
+            Class.forName(
+                    SharedUiAdapter::class.java.name,
+                    /* initialize = */ false,
+                    uiProviderBinder.javaClass.classLoader
+                )
+                .getMethod("openSession", Executor::class.java, targetSharedSessionClientClass)
+
+        override fun openSession(clientExecutor: Executor, client: SharedUiAdapter.SessionClient) {
+            try {
+                val sessionClientProxy =
+                    Proxy.newProxyInstance(
+                        uiProviderBinder.javaClass.classLoader,
+                        arrayOf(targetSharedSessionClientClass),
+                        SessionClientProxyHandler(client)
+                    )
+                openSessionMethod.invoke(uiProviderBinder, clientExecutor, sessionClientProxy)
+            } catch (exception: Throwable) {
+                client.onSessionError(exception)
+            }
+        }
+
+        private class SessionClientProxyHandler(
+            private val origClient: SharedUiAdapter.SessionClient
+        ) : InvocationHandler {
+            override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any {
+                return when (method.name) {
+                    "onSessionOpened" -> {
+                        // We have to forward the call to original client, but it won't
+                        // recognize Session class on targetClassLoader. We need proxy for it
+                        // on local ClassLoader.
+                        args!! // This method will always have an argument, so safe to !!
+                        origClient.onSessionOpened(SessionProxy(args[0]))
+                    }
+                    "onSessionError" -> {
+                        args!! // This method will always have an argument, so safe to !!
+                        val throwable = args[0] as Throwable
+                        origClient.onSessionError(throwable)
+                    }
+                    "toString" -> origClient.toString()
+                    "equals" -> proxy === args?.get(0)
+                    "hashCode" -> hashCode()
+                    else -> {
+                        throw UnsupportedOperationException(
+                            "Unexpected method call object:$proxy, method: $method, args: $args"
+                        )
+                    }
+                }
+            }
+        }
+
+        /** Create [SharedUiAdapter.Session] that proxies to [origSession] */
+        private class SessionProxy(private val origSession: Any) : SharedUiAdapter.Session {
+            private val targetClass =
+                Class.forName(
+                        SharedUiAdapter.Session::class.java.name,
+                        /* initialize = */ false,
+                        origSession.javaClass.classLoader
+                    )
+                    .also { it.cast(origSession) }
+
+            private val closeMethod = targetClass.getMethod("close")
+
+            override fun close() {
+                closeMethod.invoke(origSession)
+            }
+        }
+    }
+
+    /**
+     * [RemoteAdapter] maintains a shared session with a UI provider living in a different process.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private class RemoteAdapter(private val adapterInterface: ISharedUiAdapter) : SharedUiAdapter {
+        override fun openSession(clientExecutor: Executor, client: SharedUiAdapter.SessionClient) {
+            tryToCallRemoteObject(adapterInterface) {
+                this.openRemoteSession(RemoteSharedUiSessionClient(client, clientExecutor))
+            }
+        }
+
+        class RemoteSharedUiSessionClient(
+            val client: SharedUiAdapter.SessionClient,
+            val clientExecutor: Executor
+        ) : IRemoteSharedUiSessionClient.Stub() {
+            override fun onRemoteSessionOpened(
+                remoteSessionController: IRemoteSharedUiSessionController
+            ) {
+                clientExecutor.execute {
+                    client.onSessionOpened(SessionImpl(remoteSessionController))
+                }
+                addBinderDeathListener(remoteSessionController) {
+                    onRemoteSessionError("Remote process died")
+                }
+            }
+
+            override fun onRemoteSessionError(errorString: String) {
+                clientExecutor.execute { client.onSessionError(Throwable(errorString)) }
+            }
+
+            private class SessionImpl(
+                val remoteSessionController: IRemoteSharedUiSessionController
+            ) : SharedUiAdapter.Session {
+                override fun close() {
+                    closeRemoteSession(remoteSessionController)
+                }
+            }
+        }
+    }
+}
diff --git a/privacysandbox/ui/ui-core/api/current.txt b/privacysandbox/ui/ui-core/api/current.txt
index 8a52b96..cf45094 100644
--- a/privacysandbox/ui/ui-core/api/current.txt
+++ b/privacysandbox/ui/ui-core/api/current.txt
@@ -20,6 +20,9 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental. It may be changed in the future without notice.", level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public static @interface ExperimentalFeatures.DelegatingAdapterApi {
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental. It may be changed in the future without notice.", level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public static @interface ExperimentalFeatures.SharedUiPresentationApi {
+  }
+
   public final class SandboxedSdkViewUiInfo {
     ctor public SandboxedSdkViewUiInfo(int uiContainerWidth, int uiContainerHeight, android.graphics.Rect onScreenGeometry, float uiContainerOpacityHint);
     method public static androidx.privacysandbox.ui.core.SandboxedSdkViewUiInfo fromBundle(android.os.Bundle bundle);
@@ -97,5 +100,18 @@
     method public androidx.privacysandbox.ui.core.SessionObserver create();
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public interface SharedUiAdapter {
+    method public void openSession(java.util.concurrent.Executor clientExecutor, androidx.privacysandbox.ui.core.SharedUiAdapter.SessionClient client);
+  }
+
+  public static interface SharedUiAdapter.Session extends java.lang.AutoCloseable {
+    method public void close();
+  }
+
+  public static interface SharedUiAdapter.SessionClient {
+    method public void onSessionError(Throwable throwable);
+    method public void onSessionOpened(androidx.privacysandbox.ui.core.SharedUiAdapter.Session session);
+  }
+
 }
 
diff --git a/privacysandbox/ui/ui-core/api/restricted_current.txt b/privacysandbox/ui/ui-core/api/restricted_current.txt
index 8a52b96..cf45094 100644
--- a/privacysandbox/ui/ui-core/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-core/api/restricted_current.txt
@@ -20,6 +20,9 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental. It may be changed in the future without notice.", level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public static @interface ExperimentalFeatures.DelegatingAdapterApi {
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental. It may be changed in the future without notice.", level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public static @interface ExperimentalFeatures.SharedUiPresentationApi {
+  }
+
   public final class SandboxedSdkViewUiInfo {
     ctor public SandboxedSdkViewUiInfo(int uiContainerWidth, int uiContainerHeight, android.graphics.Rect onScreenGeometry, float uiContainerOpacityHint);
     method public static androidx.privacysandbox.ui.core.SandboxedSdkViewUiInfo fromBundle(android.os.Bundle bundle);
@@ -97,5 +100,18 @@
     method public androidx.privacysandbox.ui.core.SessionObserver create();
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public interface SharedUiAdapter {
+    method public void openSession(java.util.concurrent.Executor clientExecutor, androidx.privacysandbox.ui.core.SharedUiAdapter.SessionClient client);
+  }
+
+  public static interface SharedUiAdapter.Session extends java.lang.AutoCloseable {
+    method public void close();
+  }
+
+  public static interface SharedUiAdapter.SessionClient {
+    method public void onSessionError(Throwable throwable);
+    method public void onSessionOpened(androidx.privacysandbox.ui.core.SharedUiAdapter.Session session);
+  }
+
 }
 
diff --git a/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSharedUiSessionClient.aidl b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSharedUiSessionClient.aidl
new file mode 100644
index 0000000..8d36811
--- /dev/null
+++ b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSharedUiSessionClient.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 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.privacysandbox.ui.core;
+
+import androidx.privacysandbox.ui.core.IRemoteSharedUiSessionController;
+
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+oneway interface IRemoteSharedUiSessionClient {
+    void onRemoteSessionOpened(IRemoteSharedUiSessionController remoteSessionController);
+    void onRemoteSessionError(String exception);
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSharedUiSessionController.aidl
similarity index 64%
copy from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt
copy to privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSharedUiSessionController.aidl
index 1f106db..5f2d816 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TreeSet.kt
+++ b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSharedUiSessionController.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright (C) 2025 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,16 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.node
+package androidx.privacysandbox.ui.core;
 
-internal expect class TreeSet<E>(comparator: Comparator<in E>) {
-    fun add(element: E): Boolean
-
-    fun remove(element: E): Boolean
-
-    fun first(): E
-
-    fun contains(element: E): Boolean
-
-    fun isEmpty(): Boolean
-}
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+oneway interface IRemoteSharedUiSessionController {
+    void close();
+}
\ No newline at end of file
diff --git a/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/ISharedUiAdapter.aidl b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/ISharedUiAdapter.aidl
new file mode 100644
index 0000000..821ea44
--- /dev/null
+++ b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/ISharedUiAdapter.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 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.privacysandbox.ui.core;
+
+import androidx.privacysandbox.ui.core.IRemoteSharedUiSessionClient;
+
+ @JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+ oneway interface ISharedUiAdapter {
+    void openRemoteSession(IRemoteSharedUiSessionClient remoteSessionClient);
+ }
\ No newline at end of file
diff --git a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/ExperimentalFeatures.kt b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/ExperimentalFeatures.kt
index 17eec70..3fb2f6b8 100644
--- a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/ExperimentalFeatures.kt
+++ b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/ExperimentalFeatures.kt
@@ -24,4 +24,11 @@
     )
     @Retention(AnnotationRetention.BINARY)
     annotation class DelegatingAdapterApi
+
+    @RequiresOptIn(
+        "This API is experimental. It may be changed in the future without notice.",
+        RequiresOptIn.Level.WARNING
+    )
+    @Retention(AnnotationRetention.BINARY)
+    annotation class SharedUiPresentationApi
 }
diff --git a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/RemoteCallManager.kt b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/RemoteCallManager.kt
index 3d75a93..0454d2e 100644
--- a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/RemoteCallManager.kt
+++ b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/RemoteCallManager.kt
@@ -34,10 +34,21 @@
         tryToCallRemoteObject(remoteSessionController) { this.asBinder().linkToDeath(recipient, 0) }
     }
 
+    fun addBinderDeathListener(
+        remoteSessionController: IRemoteSharedUiSessionController,
+        recipient: IBinder.DeathRecipient
+    ) {
+        tryToCallRemoteObject(remoteSessionController) { this.asBinder().linkToDeath(recipient, 0) }
+    }
+
     fun closeRemoteSession(remoteSessionController: IRemoteSessionController) {
         tryToCallRemoteObject(remoteSessionController) { close() }
     }
 
+    fun closeRemoteSession(remoteSessionController: IRemoteSharedUiSessionController) {
+        tryToCallRemoteObject(remoteSessionController) { close() }
+    }
+
     /** Tries to call the remote object and handles exceptions if the remote object has died. */
     inline fun <RemoteObject> tryToCallRemoteObject(
         remoteObject: RemoteObject,
diff --git a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SharedUiAdapter.kt b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SharedUiAdapter.kt
new file mode 100644
index 0000000..194355f
--- /dev/null
+++ b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SharedUiAdapter.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 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.privacysandbox.ui.core
+
+import android.annotation.SuppressLint
+import java.util.concurrent.Executor
+
+/**
+ * An adapter that provides a communication channel between a UI provider and a client app, while
+ * the client is displaying shared UI, i.e. UI that can contain both client-owned and provider-owned
+ * elements.
+ */
+@SuppressLint("NullAnnotationGroup")
+@ExperimentalFeatures.SharedUiPresentationApi
+interface SharedUiAdapter {
+
+    /**
+     * Opens a new session to maintain connection with a UI provider. [client] will receive all
+     * incoming communication from the provider. All incoming calls to [client] will be made through
+     * the provided [clientExecutor].
+     */
+    fun openSession(clientExecutor: Executor, client: SessionClient)
+
+    /** A single session with the UI provider. */
+    interface Session : AutoCloseable {
+        /**
+         * Closes this session, indicating that the remote provider should dispose of associated
+         * resources and that the [SessionClient] should not receive further callback events.
+         */
+        override fun close()
+    }
+
+    /** The client of a single session that will receive callback events from an active session. */
+    interface SessionClient {
+        /**
+         * Called to report that the session was opened successfully, delivering the [Session]
+         * handle that should be used to communicate with the provider.
+         */
+        fun onSessionOpened(session: Session)
+
+        /**
+         * Called to report a terminal error in the session. No further events will be reported to
+         * this [SessionClient] and any further or currently pending calls to the [Session] that may
+         * have been in flight may be ignored.
+         */
+        fun onSessionError(throwable: Throwable)
+    }
+}
diff --git a/privacysandbox/ui/ui-provider/api/current.txt b/privacysandbox/ui/ui-provider/api/current.txt
index ed6f662..ce984ee 100644
--- a/privacysandbox/ui/ui-provider/api/current.txt
+++ b/privacysandbox/ui/ui-provider/api/current.txt
@@ -31,5 +31,9 @@
     property public abstract java.util.List<androidx.privacysandbox.ui.core.SessionObserverFactory> sessionObserverFactories;
   }
 
+  public final class SharedUiAdapterProxy {
+    method @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public static android.os.Bundle toCoreLibInfo(androidx.privacysandbox.ui.core.SharedUiAdapter);
+  }
+
 }
 
diff --git a/privacysandbox/ui/ui-provider/api/restricted_current.txt b/privacysandbox/ui/ui-provider/api/restricted_current.txt
index ed6f662..ce984ee 100644
--- a/privacysandbox/ui/ui-provider/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-provider/api/restricted_current.txt
@@ -31,5 +31,9 @@
     property public abstract java.util.List<androidx.privacysandbox.ui.core.SessionObserverFactory> sessionObserverFactories;
   }
 
+  public final class SharedUiAdapterProxy {
+    method @SuppressCompatibility @androidx.privacysandbox.ui.core.ExperimentalFeatures.SharedUiPresentationApi public static android.os.Bundle toCoreLibInfo(androidx.privacysandbox.ui.core.SharedUiAdapter);
+  }
+
 }
 
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderSharedUiAdapterDelegate.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderSharedUiAdapterDelegate.kt
new file mode 100644
index 0000000..d1b643d
--- /dev/null
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderSharedUiAdapterDelegate.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2025 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.
+ */
+@file:JvmName("SharedUiAdapterProxy")
+
+package androidx.privacysandbox.ui.provider
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.privacysandbox.ui.core.ExperimentalFeatures
+import androidx.privacysandbox.ui.core.IRemoteSharedUiSessionClient
+import androidx.privacysandbox.ui.core.IRemoteSharedUiSessionController
+import androidx.privacysandbox.ui.core.ISharedUiAdapter
+import androidx.privacysandbox.ui.core.SharedUiAdapter
+import java.util.concurrent.Executor
+
+/**
+ * Provides a [Bundle] containing a Binder which represents a [SharedUiAdapter]. The Bundle is sent
+ * to the client in order for the [SharedUiAdapter] to be used to maintain a connection with a UI
+ * provider.
+ */
+@SuppressLint("NullAnnotationGroup")
+@ExperimentalFeatures.SharedUiPresentationApi
+fun SharedUiAdapter.toCoreLibInfo(): Bundle {
+    val binderAdapter = BinderSharedUiAdapterDelegate(this)
+    // TODO(b/350445624): Add version info
+    val bundle = Bundle()
+
+    // Bundle key is a binary compatibility requirement
+    bundle.putBinder("sharedUiAdapterBinder", binderAdapter)
+    return bundle
+}
+
+@SuppressLint("NullAnnotationGroup")
+@OptIn(ExperimentalFeatures.SharedUiPresentationApi::class)
+private class BinderSharedUiAdapterDelegate(private val adapter: SharedUiAdapter) :
+    ISharedUiAdapter.Stub(), SharedUiAdapter {
+
+    override fun openSession(clientExecutor: Executor, client: SharedUiAdapter.SessionClient) {
+        adapter.openSession(clientExecutor, client)
+    }
+
+    // TODO(b/365614954): try to improve method's performance.
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    override fun openRemoteSession(remoteSessionClient: IRemoteSharedUiSessionClient) {
+        try {
+            val sessionClient = SessionClientProxy(remoteSessionClient)
+            openSession(Runnable::run, sessionClient)
+        } catch (exception: Throwable) {
+            remoteSessionClient.onRemoteSessionError(exception.message)
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private class SessionClientProxy(
+        private val remoteSessionClient: IRemoteSharedUiSessionClient
+    ) : SharedUiAdapter.SessionClient {
+        override fun onSessionOpened(session: SharedUiAdapter.Session) {
+            remoteSessionClient.onRemoteSessionOpened(RemoteSharedUiSessionController(session))
+        }
+
+        override fun onSessionError(throwable: Throwable) {
+            remoteSessionClient.onRemoteSessionError(throwable.message)
+        }
+
+        private class RemoteSharedUiSessionController(val session: SharedUiAdapter.Session) :
+            IRemoteSharedUiSessionController.Stub() {
+            override fun close() {
+                session.close()
+            }
+        }
+    }
+}
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/SharedSessionIntegrationTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/SharedSessionIntegrationTests.kt
new file mode 100644
index 0000000..71c309c
--- /dev/null
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/SharedSessionIntegrationTests.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2025 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.privacysandbox.ui.tests.endtoend
+
+import android.os.Bundle
+import androidx.privacysandbox.ui.client.SharedUiAdapterFactory
+import androidx.privacysandbox.ui.core.BackwardCompatUtil
+import androidx.privacysandbox.ui.core.ExperimentalFeatures
+import androidx.privacysandbox.ui.core.SharedUiAdapter
+import androidx.privacysandbox.ui.provider.toCoreLibInfo
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+// TODO(b/263460954): in the following CLs with AppOwnedUiContainer implementation:
+//  1) Add tests for cases when the adapter is set on an app-owned container;
+//  2) Add state change listener checks to the tests;
+@OptIn(ExperimentalFeatures.SharedUiPresentationApi::class)
+@RunWith(Parameterized::class)
+@MediumTest
+class SharedSessionIntegrationTests(private val invokeBackwardsCompatFlow: Boolean) {
+
+    companion object {
+        const val TIMEOUT = 1000L
+        const val TEST_ONLY_USE_REMOTE_ADAPTER = "testOnlyUseRemoteAdapter"
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "invokeBackwardsCompatFlow={0}")
+        fun data(): Array<Any> =
+            arrayOf(
+                arrayOf(true),
+                arrayOf(false),
+            )
+    }
+
+    @get:Rule var activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
+
+    @Before
+    fun setup() {
+        if (!invokeBackwardsCompatFlow) {
+            // Device needs to support remote provider to invoke non-backward-compat flow.
+            assumeTrue(BackwardCompatUtil.canProviderBeRemote())
+        }
+    }
+
+    @Test
+    fun testOpenSession_fromAdapter() {
+        val client = TestSessionClient()
+
+        val adapter = createAdapterAndEstablishSession(client)
+
+        assertThat(adapter.session).isNotNull()
+        assertThat(client.isSessionOpened).isTrue()
+    }
+
+    @Test
+    fun testSessionError() {
+        val client = TestSessionClient()
+
+        createAdapterAndEstablishSession(client, isFailingSession = true)
+
+        assertThat(client.isSessionErrorCalled).isTrue()
+    }
+
+    @Test
+    fun testCloseSession() {
+        val client = TestSessionClient()
+        val adapter = createAdapterAndEstablishSession(client)
+
+        client.closeClient()
+
+        assertThat(client.isClientClosed).isTrue()
+        assertWithMessage("close is called on Session").that(adapter.isCloseSessionCalled).isTrue()
+    }
+
+    @Test
+    fun testSessionClientProxy_methodsOnObjectClass() {
+        // Only makes sense when a dynamic proxy is involved in the flow
+        assumeTrue(invokeBackwardsCompatFlow)
+        val testSessionClient = TestSessionClient()
+
+        val sdkAdapter =
+            createAdapterAndEstablishSession(testSharedSessionClient = testSessionClient)
+        // Verify toString, hashCode and equals have been implemented for dynamic proxy
+        val testSession = sdkAdapter.session as TestSharedUiAdapter.TestSession
+        val client = testSession.sessionClient
+
+        assertThat(client.toString()).isEqualTo(testSessionClient.toString())
+        assertThat(client.equals(client)).isTrue()
+        assertThat(client).isNotEqualTo(testSessionClient)
+        assertThat(client.hashCode()).isEqualTo(client.hashCode())
+    }
+
+    // TODO (b/263460954): add app-owned container as a parameter once it's implemented
+    private fun createAdapterAndEstablishSession(
+        testSharedSessionClient: TestSessionClient = TestSessionClient(),
+        isFailingSession: Boolean = false
+    ): TestSharedUiAdapter {
+        val adapter = TestSharedUiAdapter(isFailingSession)
+        val adapterFromCoreLibInfo =
+            SharedUiAdapterFactory.createFromCoreLibInfo(getCoreLibInfoFromAdapter(adapter))
+        adapterFromCoreLibInfo.openSession(Runnable::run, testSharedSessionClient)
+
+        assertWithMessage("openSession is called on adapter")
+            .that(adapter.isOpenSessionCalled)
+            .isTrue()
+        return adapter
+    }
+
+    private fun getCoreLibInfoFromAdapter(sdkAdapter: SharedUiAdapter): Bundle {
+        val bundle = sdkAdapter.toCoreLibInfo()
+        bundle.putBoolean(TEST_ONLY_USE_REMOTE_ADAPTER, !invokeBackwardsCompatFlow)
+        return bundle
+    }
+
+    inner class TestSharedUiAdapter(private val isFailingSession: Boolean = false) :
+        SharedUiAdapter {
+        private val openSessionLatch: CountDownLatch = CountDownLatch(1)
+        private val closeSessionLatch: CountDownLatch = CountDownLatch(1)
+
+        val isOpenSessionCalled: Boolean
+            get() = openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
+
+        val isCloseSessionCalled: Boolean
+            get() = closeSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
+
+        lateinit var session: SharedUiAdapter.Session
+
+        override fun openSession(clientExecutor: Executor, client: SharedUiAdapter.SessionClient) {
+            session =
+                if (isFailingSession) FailingTestSession(client, clientExecutor)
+                else TestSession(client)
+            client.onSessionOpened(session)
+            openSessionLatch.countDown()
+        }
+
+        inner class TestSession(val sessionClient: SharedUiAdapter.SessionClient) :
+            SharedUiAdapter.Session {
+            override fun close() {
+                closeSessionLatch.countDown()
+            }
+        }
+
+        inner class FailingTestSession(
+            val sessionClient: SharedUiAdapter.SessionClient,
+            clientExecutor: Executor
+        ) : SharedUiAdapter.Session {
+            init {
+                clientExecutor.execute {
+                    sessionClient.onSessionError(Throwable("Test Session Exception"))
+                }
+            }
+
+            override fun close() {
+                closeSessionLatch.countDown()
+            }
+        }
+    }
+
+    inner class TestSessionClient : SharedUiAdapter.SessionClient {
+        private val sessionOpenedLatch = CountDownLatch(1)
+        private val sessionErrorLatch = CountDownLatch(1)
+        private val closeClientLatch = CountDownLatch(1)
+
+        private var session: SharedUiAdapter.Session? = null
+            get() {
+                sessionOpenedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
+                return field
+            }
+
+        val isSessionOpened: Boolean
+            get() = sessionOpenedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
+
+        val isSessionErrorCalled: Boolean
+            get() = sessionErrorLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
+
+        val isClientClosed: Boolean
+            get() = closeClientLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
+
+        fun closeClient() {
+            val localSession = session
+            if (localSession != null) {
+                localSession.close()
+                closeClientLatch.countDown()
+            }
+        }
+
+        override fun onSessionOpened(session: SharedUiAdapter.Session) {
+            this.session = session
+            sessionOpenedLatch.countDown()
+        }
+
+        override fun onSessionError(throwable: Throwable) {
+            sessionErrorLatch.countDown()
+        }
+    }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index a6bf15d..67cfebb 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -110,7 +110,23 @@
         }
         val resolvedTypeArguments: Map<String, KSTypeArgument> =
             ksType.declaration.typeParameters
-                .mapIndexed { i, parameter -> parameter.name.asString() to ksType.arguments[i] }
+                .mapIndexed { i, parameter ->
+                    val argument: KSTypeArgument =
+                        if (ksType.arguments.isNotEmpty()) {
+                            ksType.arguments[i]
+                        } else {
+                            // In KSP2, a raw java KSType doesn't have any arguments, but we need
+                            // them to replace type parameters in super types (we are forced to
+                            // create super types from the declaration because KSType itself doesn't
+                            // have super types). Here, we mimic KSP1 behavior by taking the first
+                            // bound type as the type argument (e.g. 'Bar' in 'T extends Bar & Baz')
+                            env.resolver.getTypeArgument(
+                                parameter.bounds.first(),
+                                Variance.INVARIANT
+                            )
+                        }
+                    parameter.name.asString() to argument
+                }
                 .toMap()
         val superTypes =
             (ksType.declaration as? KSClassDeclaration)?.superTypes?.toList()?.map {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 9aa9ad9..4dc06c5 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -1178,6 +1178,135 @@
     }
 
     @Test
+    fun rawTypeSuperType() {
+        runProcessorTest(
+            sources =
+                listOf(
+                    Source.java(
+                        "test.Subject",
+                        """
+                        package test;
+                        import java.util.HashSet;
+                        import java.util.Set;
+                        class Subject {
+                            Foo rawFoo;
+                        }
+                        interface Foo<T> extends FooSuper<T> {}
+                        interface FooSuper<T> {}
+                        """
+                            .trimIndent()
+                    )
+                ),
+        ) { invocation ->
+            val subject = invocation.processingEnv.requireTypeElement("test.Subject")
+            val rawFoo = subject.getDeclaredField("rawFoo")
+
+            assertThat(rawFoo.type.asTypeName())
+                .isEqualTo(XClassName.get("test", "Foo").copy(nullable = true))
+            assertThat(rawFoo.type.superTypes.map { it.asTypeName() })
+                .containsExactly(
+                    ANY_OBJECT,
+                    // In KSP there's no API for creating java raw KSTypes, so the FooSuper we
+                    // create for XType#superTypes is forced to contain type arguments.
+                    if (invocation.isKsp) {
+                        XClassName.get("test", "FooSuper")
+                            .parametrizedBy(ANY_OBJECT.copy(nullable = true))
+                    } else {
+                        XClassName.get("test", "FooSuper")
+                    }
+                )
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun rawTypeSuperType_singleBounds() {
+        runProcessorTest(
+            sources =
+                listOf(
+                    Source.java(
+                        "test.Subject",
+                        """
+                        package test;
+                        import java.util.HashSet;
+                        import java.util.Set;
+                        class Subject {
+                            Foo rawFoo;
+                        }
+                        interface Foo<T extends Bar> extends FooSuper<T> {}
+                        interface FooSuper<T> {}
+                        interface Bar {}
+                        """
+                            .trimIndent()
+                    )
+                ),
+        ) { invocation ->
+            val subject = invocation.processingEnv.requireTypeElement("test.Subject")
+            val rawFoo = subject.getDeclaredField("rawFoo")
+
+            assertThat(rawFoo.type.asTypeName())
+                .isEqualTo(XClassName.get("test", "Foo").copy(nullable = true))
+            assertThat(rawFoo.type.superTypes.map { it.asTypeName() })
+                .containsExactly(
+                    ANY_OBJECT,
+                    // In KSP there's no API for creating java raw KSTypes, so the FooSuper we
+                    // create for XType#superTypes is forced to contain type arguments.
+                    if (invocation.isKsp) {
+                        XClassName.get("test", "FooSuper")
+                            .parametrizedBy(XClassName.get("test", "Bar").copy(nullable = true))
+                    } else {
+                        XClassName.get("test", "FooSuper")
+                    }
+                )
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun rawTypeSuperType_multipleBounds() {
+        runProcessorTest(
+            sources =
+                listOf(
+                    Source.java(
+                        "test.Subject",
+                        """
+                        package test;
+                        import java.util.HashSet;
+                        import java.util.Set;
+                        class Subject {
+                            Foo rawFoo;
+                        }
+                        interface Foo<T extends Bar & Baz> extends FooSuper<T> {}
+                        interface FooSuper<T> {}
+                        interface Bar {}
+                        interface Baz {}
+                        """
+                            .trimIndent()
+                    )
+                ),
+        ) { invocation ->
+            val subject = invocation.processingEnv.requireTypeElement("test.Subject")
+            val rawFoo = subject.getDeclaredField("rawFoo")
+
+            assertThat(rawFoo.type.asTypeName())
+                .isEqualTo(XClassName.get("test", "Foo").copy(nullable = true))
+            assertThat(rawFoo.type.superTypes.map { it.asTypeName() })
+                .containsExactly(
+                    XTypeName.ANY_OBJECT,
+                    // In KSP there's no API for creating java raw KSTypes, so the FooSuper we
+                    // create for XType#superTypes is forced to contain type arguments.
+                    if (invocation.isKsp) {
+                        XClassName.get("test", "FooSuper")
+                            .parametrizedBy(XClassName.get("test", "Bar").copy(nullable = true))
+                    } else {
+                        XClassName.get("test", "FooSuper")
+                    }
+                )
+                .inOrder()
+        }
+    }
+
+    @Test
     fun isKotlinUnit() {
         val kotlinSubject =
             Source.kotlin(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 1c364db..ddd8d8d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -811,9 +811,10 @@
             context.processingEnv.requireType(CommonTypeNames.COLLECTION).rawType
         if (collectionTypeRaw.isAssignableFrom(mapValueTypeArg.rawType)) {
             // The Map's value type argument is assignable to a Collection, we need to make
-            // sure it is either a list or a set.
-            val listTypeRaw = context.processingEnv.requireType(CommonTypeNames.LIST).rawType
-            val setTypeRaw = context.processingEnv.requireType(CommonTypeNames.SET).rawType
+            // sure it is either a List or a Set.
+            val listTypeRaw =
+                context.processingEnv.requireType(CommonTypeNames.MUTABLE_LIST).rawType
+            val setTypeRaw = context.processingEnv.requireType(CommonTypeNames.MUTABLE_SET).rawType
             val collectionValueType =
                 when {
                     mapValueTypeArg.rawType.isAssignableFrom(listTypeRaw) ->
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
index 50370df..4ae5138 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -568,7 +568,7 @@
             relation.field.type.let { fieldType ->
                 if (fieldType.typeArguments.isNotEmpty()) {
                     val rawType = fieldType.rawType
-                    val setType = context.processingEnv.requireType(CommonTypeNames.SET)
+                    val setType = context.processingEnv.requireType(CommonTypeNames.MUTABLE_SET)
                     val paramTypeName =
                         if (setType.rawType.isAssignableFrom(rawType)) {
                             when (context.codeLanguage) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt b/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
deleted file mode 100644
index 76f8fb9..0000000
--- a/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2024 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.room
-
-import androidx.room.compiler.processing.XProcessingEnvConfig
-import androidx.room.compiler.processing.XProcessingStep
-import androidx.room.compiler.processing.util.CompilationResultSubject
-import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
-import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.compiler.processing.util.runProcessorTest
-import com.google.devtools.ksp.processing.SymbolProcessorProvider
-import java.io.File
-import javax.annotation.processing.Processor
-
-fun runProcessorTestWithK1(
-    sources: List<Source> = emptyList(),
-    classpath: List<File> = emptyList(),
-    options: Map<String, String> = emptyMap(),
-    javacArguments: List<String> = emptyList(),
-    kotlincArguments: List<String> = emptyList(),
-    handler: (XTestInvocation) -> Unit
-) {
-    androidx.room.compiler.processing.util.runProcessorTest(
-        sources = sources,
-        classpath = classpath,
-        options = options,
-        javacArguments = javacArguments,
-        kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
-        handler = handler
-    )
-}
-
-fun runProcessorTestWithK1(
-    sources: List<Source> = emptyList(),
-    classpath: List<File> = emptyList(),
-    options: Map<String, String> = emptyMap(),
-    javacArguments: List<String> = emptyList(),
-    kotlincArguments: List<String> = emptyList(),
-    createProcessingSteps: () -> Iterable<XProcessingStep>,
-    onCompilationResult: (CompilationResultSubject) -> Unit
-) {
-    androidx.room.compiler.processing.util.runProcessorTest(
-        sources = sources,
-        classpath = classpath,
-        options = options,
-        javacArguments = javacArguments,
-        kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
-        createProcessingSteps = createProcessingSteps,
-        onCompilationResult = onCompilationResult
-    )
-}
-
-fun runProcessorTestWithK1(
-    sources: List<Source> = emptyList(),
-    classpath: List<File> = emptyList(),
-    options: Map<String, String> = emptyMap(),
-    javacArguments: List<String> = emptyList(),
-    kotlincArguments: List<String> = emptyList(),
-    javacProcessors: List<Processor>,
-    symbolProcessorProviders: List<SymbolProcessorProvider>,
-    onCompilationResult: (CompilationResultSubject) -> Unit
-) {
-    runProcessorTest(
-        sources = sources,
-        classpath = classpath,
-        options = options,
-        javacArguments = javacArguments,
-        kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
-        javacProcessors = javacProcessors,
-        symbolProcessorProviders = symbolProcessorProviders,
-        onCompilationResult = onCompilationResult
-    )
-}
-
-fun runKspTestWithK1(
-    sources: List<Source>,
-    classpath: List<File> = emptyList(),
-    options: Map<String, String> = emptyMap(),
-    javacArguments: List<String> = emptyList(),
-    kotlincArguments: List<String> = emptyList(),
-    config: XProcessingEnvConfig? = null,
-    handler: (XTestInvocation) -> Unit
-) {
-    if (config != null) {
-        runKspTest(
-            sources = sources,
-            classpath = classpath,
-            options = options,
-            javacArguments = javacArguments,
-            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
-            config = config,
-            handler = handler
-        )
-    } else {
-        runKspTest(
-            sources = sources,
-            classpath = classpath,
-            options = options,
-            javacArguments = javacArguments,
-            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS + kotlincArguments,
-            handler = handler
-        )
-    }
-}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
index 23af19e..dcac814 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
@@ -25,7 +25,7 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -272,7 +272,7 @@
             } else {
                 sources to emptyList()
             }
-        runProcessorTestWithK1(sources = sources, classpath = classpath, handler = handler)
+        runProcessorTest(sources = sources, classpath = classpath, handler = handler)
     }
 
     private fun XTestInvocation.objectMethodNames(): List<String> {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
index cc19974..9927769 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
@@ -20,7 +20,7 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import org.junit.Test
 
 class SQLTypeAffinityTest {
@@ -33,7 +33,7 @@
      */
     @Test
     fun affinityTypes() {
-        runProcessorTestWithK1(sources = emptyList()) { invocation ->
+        runProcessorTest(sources = emptyList()) { invocation ->
             fun XNullability.toSignature() =
                 if (invocation.isKsp) {
                     when (this) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
index 5c21636..c567a4d 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.processor
 
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.migration.bundle.EntityBundle
 import androidx.room.migration.bundle.FieldBundle
@@ -24,7 +25,6 @@
 import androidx.room.migration.bundle.SchemaBundle
 import androidx.room.processor.ProcessorErrors.AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR
 import androidx.room.processor.ProcessorErrors.INNER_CLASS_AUTOMIGRATION_SPEC_MUST_BE_STATIC
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import org.junit.Test
 
@@ -44,7 +44,7 @@
                     .trimIndent()
             )
 
-        runProcessorTestWithK1(listOf(source)) { invocation ->
+        runProcessorTest(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
@@ -71,7 +71,7 @@
                     .trimIndent()
             )
 
-        runProcessorTestWithK1(listOf(source)) { invocation ->
+        runProcessorTest(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
@@ -100,7 +100,7 @@
                     .trimIndent()
             )
 
-        runProcessorTestWithK1(listOf(source)) { invocation ->
+        runProcessorTest(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec =
@@ -132,7 +132,7 @@
                     .trimIndent()
             )
 
-        runProcessorTestWithK1(listOf(source)) { invocation ->
+        runProcessorTest(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
index 48fe74f..ff64ec8 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
@@ -4,7 +4,7 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.util.Source
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.testing.context
 import androidx.room.vo.Dao
 import androidx.room.writer.DaoWriter
@@ -209,7 +209,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val dbElm = invocation.context.processingEnv.requireTypeElement("MyDb")
             val dbType = dbElm.type
             // if we could create valid code, it is good, no need for assertions.
@@ -269,8 +269,8 @@
             """
             )
         // https://github.com/google/ksp/issues/2051
-        runProcessorTestWithK1(sources = listOf(baseClass, extension, COMMON.USER, fakeDb)) {
-            invocation ->
+        runProcessorTest(sources = listOf(baseClass, extension, COMMON.USER, fakeDb)) { invocation
+            ->
             val daoElm = invocation.processingEnv.requireTypeElement("foo.bar.MyDao")
             val dbElm = invocation.context.processingEnv.requireTypeElement("foo.bar.MyDb")
             val dbType = dbElm.type
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
index abdc2d7..2147ee9 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
@@ -19,7 +19,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.testing.context
 import androidx.room.vo.Entity
 import java.io.File
@@ -61,7 +61,7 @@
         } else {
             baseClassReplacement = " extends $baseClass"
         }
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 sources +
                     Source.java(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
index 9ac795b..5d44695 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.testing.context
 import androidx.room.vo.FtsEntity
 import java.io.File
@@ -76,8 +76,7 @@
                     baseClassReplacement
                 ) + input + ENTITY_SUFFIX
             )
-        runProcessorTestWithK1(sources = sources + entitySource, classpath = classpath) { invocation
-            ->
+        runProcessorTest(sources = sources + entitySource, classpath = classpath) { invocation ->
             val entity = invocation.processingEnv.requireTypeElement("foo.bar.MyEntity")
             val processor = FtsTableEntityProcessor(invocation.context, entity)
             val processedEntity = processor.process()
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
index 5027258..37b832a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
@@ -28,6 +28,7 @@
 import androidx.room.compiler.codegen.compat.XConverters.toString
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.CommonTypeNames.MUTABLE_LIST
 import androidx.room.ext.CommonTypeNames.STRING
@@ -36,7 +37,6 @@
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CustomTypeConverter
 import com.squareup.javapoet.TypeVariableName
@@ -256,7 +256,7 @@
                         .build()
                         .toString(CodeLanguage.JAVA)
             )
-        runProcessorTestWithK1(sources = listOf(baseConverter, extendingClass)) { invocation ->
+        runProcessorTest(sources = listOf(baseConverter, extendingClass)) { invocation ->
             val element =
                 invocation.processingEnv.requireTypeElement(extendingClassName.canonicalName)
             val converter =
@@ -339,7 +339,7 @@
                 public class Container {}
                 """
             )
-        runProcessorTestWithK1(listOf(source)) { invocation ->
+        runProcessorTest(listOf(source)) { invocation ->
             val result =
                 CustomConverterProcessor.findConverters(
                     invocation.context,
@@ -399,7 +399,7 @@
         vararg sources: Source,
         handler: (CustomTypeConverter?, XTestInvocation) -> Unit
     ) {
-        runProcessorTestWithK1(sources = sources.toList() + CONTAINER) { invocation ->
+        runProcessorTest(sources = sources.toList() + CONTAINER) { invocation ->
             val processed =
                 CustomConverterProcessor.findConverters(
                     invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
index c8303f2..fe57d21 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -22,11 +22,11 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.runKspTest
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.ProcessorErrors.nullableCollectionOrArrayReturnTypeInDaoFunction
 import androidx.room.processor.ProcessorErrors.nullableComponentInDaoFunctionReturnType
-import androidx.room.runKspTestWithK1
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.Dao
 import androidx.room.vo.ReadQueryFunction
@@ -264,7 +264,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(daoSrc) + COMMON.USER) { invocation ->
+        runProcessorTest(sources = listOf(daoSrc) + COMMON.USER) { invocation ->
             val dao =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Dao::class.qualifiedName!!)
@@ -453,7 +453,7 @@
         """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val dao = invocation.processingEnv.requireTypeElement("MyDao")
             val dbType = invocation.context.processingEnv.requireType(ROOM_DB)
             DaoProcessor(
@@ -491,7 +491,7 @@
             """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -529,7 +529,7 @@
             """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -570,7 +570,7 @@
             """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -642,7 +642,7 @@
             """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -768,7 +768,7 @@
             """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -839,7 +839,7 @@
         classpathFiles: List<File> = emptyList(),
         handler: (Dao, XTestInvocation) -> Unit
     ) {
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     Source.java("foo.bar.MyDao", DAO_PREFIX + inputs.joinToString("\n")),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTargetFunctionTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTargetFunctionTest.kt
index 3a1f875b..6a6fc607 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTargetFunctionTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTargetFunctionTest.kt
@@ -19,7 +19,7 @@
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.testing.context
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -461,7 +461,7 @@
     }
 
     private fun singleRun(vararg sources: Source, handler: ((XTestInvocation) -> Unit)? = null) {
-        runProcessorTestWithK1(sources = sources.toList()) { invocation ->
+        runProcessorTest(sources = sources.toList()) { invocation ->
             DataClassProcessor.createFor(
                     context = invocation.context,
                     element = invocation.processingEnv.requireTypeElement(MY_DATA_CLASS),
@@ -507,7 +507,7 @@
         val autoValueDataClassSource =
             Source.java(AUTOVALUE_MY_DATA_CLASS.canonicalName, autoValueDataClassCode)
         val all = sources.toList() + dataClassSource + autoValueDataClassSource
-        return runProcessorTestWithK1(sources = all) { invocation ->
+        return runProcessorTest(sources = all) { invocation ->
             DataClassProcessor.createFor(
                     context = invocation.context,
                     element = invocation.processingEnv.requireTypeElement(MY_DATA_CLASS),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTest.kt
index adbf0ac..de68128 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DataClassProcessorTest.kt
@@ -23,6 +23,7 @@
 import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
@@ -33,7 +34,6 @@
 import androidx.room.processor.ProcessorErrors.relationCannotFindJunctionEntityField
 import androidx.room.processor.ProcessorErrors.relationCannotFindJunctionParentField
 import androidx.room.processor.ProcessorErrors.relationCannotFindParentEntityField
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CallType
 import androidx.room.vo.Constructor
@@ -85,7 +85,7 @@
                 public void setBaseField(String baseField){ }
             }
         """
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     Source.java(
@@ -1063,7 +1063,7 @@
             $FOOTER
             """
             )
-        runProcessorTestWithK1(sources = listOf(dataClass)) { invocation ->
+        runProcessorTest(sources = listOf(dataClass)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement(MY_DATA_CLASS)
             val dataClass1 =
                 DataClassProcessor.createFor(
@@ -1723,7 +1723,7 @@
                 "foo.bar.TestData.WithJvmOverloads"
             )
             .forEach {
-                runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
+                runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
                     DataClassProcessor.createFor(
                             context = invocation.context,
                             element = invocation.processingEnv.requireTypeElement(it),
@@ -1738,7 +1738,7 @@
 
     @Test
     fun dataClass_withJvmOverloads_primaryConstructor() {
-        runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
+        runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
             DataClassProcessor.createFor(
                     context = invocation.context,
                     element =
@@ -1768,7 +1768,7 @@
             }
             """
             )
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val dataClass =
                 DataClassProcessor.createFor(
                         context = invocation.context,
@@ -1784,7 +1784,7 @@
 
     @Test
     fun ignoredColumns_noConstructor() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -1823,7 +1823,7 @@
 
     @Test
     fun ignoredColumns_noSetterGetter() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -1860,7 +1860,7 @@
 
     @Test
     fun ignoredColumns_columnInfo() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -1892,7 +1892,7 @@
 
     @Test
     fun ignoredColumns_missing() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -1926,7 +1926,7 @@
 
     @Test
     fun noSetter_scopeBindStmt() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -1955,7 +1955,7 @@
 
     @Test
     fun noSetter_scopeTwoWay() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -1987,7 +1987,7 @@
 
     @Test
     fun noSetter_scopeReadFromCursor() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -2019,7 +2019,7 @@
 
     @Test
     fun noGetter_scopeBindStmt() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -2051,7 +2051,7 @@
 
     @Test
     fun noGetter_scopeTwoWay() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -2083,7 +2083,7 @@
 
     @Test
     fun noGetter_scopeReadCursor() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.java(
                     MY_DATA_CLASS.canonicalName,
@@ -2112,7 +2112,7 @@
 
     @Test
     fun setterStartsWithIs() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             listOf(
                 Source.kotlin(
                     "Book.kt",
@@ -2141,7 +2141,7 @@
                 .isEqualTo(
                     FieldGetter(
                         fieldName = "isbn",
-                        jvmName = "getIsbn",
+                        jvmName = if (invocation.isKsp2) "isbn" else "getIsbn",
                         type = stringType,
                         callType = CallType.SYNTHETIC_METHOD
                     )
@@ -2160,7 +2160,7 @@
                 .isEqualTo(
                     FieldGetter(
                         fieldName = "isbn2",
-                        jvmName = "getIsbn2",
+                        jvmName = if (invocation.isKsp2) "isbn2" else "getIsbn2",
                         type = stringType.makeNullable(),
                         callType = CallType.SYNTHETIC_METHOD
                     )
@@ -2169,7 +2169,7 @@
                 .isEqualTo(
                     FieldSetter(
                         fieldName = "isbn2",
-                        jvmName = "setIsbn2",
+                        jvmName = if (invocation.isKsp2) "setbn2" else "setIsbn2",
                         type = stringType.makeNullable(),
                         callType = CallType.SYNTHETIC_METHOD
                     )
@@ -2180,7 +2180,7 @@
     @Test
     fun embedded_nullability() {
         listOf("foo.bar.TestData.SomeEmbeddedVals").forEach {
-            runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
+            runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
                 val result =
                     DataClassProcessor.createFor(
                             context = invocation.context,
@@ -2228,7 +2228,7 @@
     ) {
         val dataClassSource = Source.java(MY_DATA_CLASS.canonicalName, code)
         val all = sources.toList() + dataClassSource
-        runProcessorTestWithK1(sources = all, classpath = classpath) { invocation ->
+        runProcessorTest(sources = all, classpath = classpath) { invocation ->
             handler.invoke(
                 DataClassProcessor.createFor(
                         context = invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
index d66a941..44ec92f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
@@ -19,7 +19,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.runKspTestWithK1
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.testing.context
 import org.junit.Test
 
@@ -136,7 +136,7 @@
     }
 
     private fun runTest(constructorSource: Source, handler: (XTestInvocation) -> Unit = { _ -> }) {
-        runKspTestWithK1(sources = listOf(databaseSource, constructorSource)) { invocation ->
+        runKspTest(sources = listOf(databaseSource, constructorSource)) { invocation ->
             val entity =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 0dccc4d..ca9411c 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -29,12 +29,12 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.compileFilesIntoJar
+import androidx.room.compiler.processing.util.runKspTest
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.ParsedQuery
 import androidx.room.parser.QueryType
 import androidx.room.parser.Table
 import androidx.room.processor.ProcessorErrors.invalidAutoMigrationSchema
-import androidx.room.runKspTestWithK1
-import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.query.result.DataClassRowAdapter
 import androidx.room.solver.query.result.EntityRowAdapter
 import androidx.room.testing.context
@@ -511,7 +511,7 @@
                 }
                 """
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
             createProcessingSteps = { listOf(DatabaseProcessingStep()) }
@@ -1260,7 +1260,7 @@
                 }
                 """
             )
-        runProcessorTestWithK1(sources = listOf(badDaoType)) { invocation ->
+        runProcessorTest(sources = listOf(badDaoType)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
             val result =
                 DatabaseProcessor(baseContext = invocation.context, element = element).process()
@@ -1284,7 +1284,7 @@
                 }
                 """
             )
-        runProcessorTestWithK1(listOf(badDaoType)) { invocation ->
+        runProcessorTest(listOf(badDaoType)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
             val result =
                 DatabaseProcessor(baseContext = invocation.context, element = element).process()
@@ -1506,7 +1506,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(dbSource, USER),
             options = mapOf("room.schemaLocation" to "schemas/", "room.generateKotlin" to "false")
         ) { invocation ->
@@ -1564,7 +1564,7 @@
                         .trimIndent()
                 )
             }
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(dbSource, USER),
             javacProcessors = listOf(RoomProcessor()),
             symbolProcessorProviders = listOf(RoomKspProcessor.Provider()),
@@ -1594,7 +1594,7 @@
                 }
                 """
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(jvmNameInDaoGetter),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -1633,7 +1633,7 @@
             """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -1666,7 +1666,7 @@
         views: Map<String, Set<String>>,
         body: (List<DatabaseView>, XTestInvocation) -> Unit
     ) {
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(DB3, BOOK),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -1719,7 +1719,7 @@
                 }
                 """
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(BOOK, bookDao) + dbs,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
             createProcessingSteps = { listOf(DatabaseProcessingStep()) },
@@ -1734,7 +1734,7 @@
         classpath: List<File> = emptyList(),
         handler: (Database, XTestInvocation) -> Unit
     ) {
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = otherFiles.toList() + Source.java("foo.bar.MyDb", DATABASE_PREFIX + input),
             classpath = classpath,
             options =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
index a00de28..d9d5117 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
@@ -19,9 +19,9 @@
 import androidx.kruth.assertThat
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.ParserErrors
 import androidx.room.parser.SQLTypeAffinity
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.verifier.ColumnInfo
 import androidx.room.vo.DatabaseView
@@ -235,7 +235,7 @@
         verify: Boolean = true,
         handler: (view: DatabaseView, invocation: XTestInvocation) -> Unit
     ) {
-        runProcessorTestWithK1(sources = sources + Source.java(name, DATABASE_PREFIX + input)) {
+        runProcessorTest(sources = sources + Source.java(name, DATABASE_PREFIX + input)) {
             invocation ->
             val view = invocation.processingEnv.requireTypeElement(name)
             val verifier =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutFunctionProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutFunctionProcessorTest.kt
index 2fd3542..c734c12 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutFunctionProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutFunctionProcessorTest.kt
@@ -28,6 +28,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
@@ -35,7 +36,6 @@
 import androidx.room.ext.ReactiveStreamsTypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.DeleteOrUpdateShortcutFunction
 import kotlin.reflect.KClass
@@ -247,13 +247,21 @@
                 @${annotation.java.canonicalName}
                 abstract public void users(User[] users);
                 """
-        ) { shortcut, _ ->
+        ) { shortcut, invocation ->
             assertThat(shortcut.element.jvmName).isEqualTo("users")
             assertThat(shortcut.parameters.size).isEqualTo(1)
             val param = shortcut.parameters.first()
             assertThat(param.type.asTypeName())
                 .isEqualTo(
-                    XTypeName.getArrayName(COMMON.USER_TYPE_NAME.copy(nullable = true))
+                    XTypeName.getArrayName(
+                            if (invocation.isKsp2) {
+                                XTypeName.getProducerExtendsName(
+                                    COMMON.USER_TYPE_NAME.copy(nullable = true)
+                                )
+                            } else {
+                                COMMON.USER_TYPE_NAME.copy(nullable = true)
+                            }
+                        )
                         .copy(nullable = true)
                 )
 
@@ -776,7 +784,7 @@
                 COMMON.LISTENABLE_FUTURE,
                 COMMON.GUAVA_ROOM
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -832,7 +840,7 @@
                 COMMON.GUAVA_ROOM
             )
 
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
index 572554b..1ad15db 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
@@ -27,7 +27,6 @@
 import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.Collate
 import androidx.room.parser.SQLTypeAffinity
-import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.types.ColumnTypeAdapter
 import androidx.room.testing.context
 import androidx.room.vo.Field
@@ -711,7 +710,7 @@
                 ),
                 ARRAY_CONVERTER
             )
-        runProcessorTestWithK1(sources = sources) { invocation ->
+        runProcessorTest(sources = sources) { invocation ->
             val (owner, fieldElement) =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(Entity::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
index 9d32327..b8ed3770 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
@@ -20,9 +20,9 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.FtsVersion
 import androidx.room.parser.SQLTypeAffinity
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CallType
 import androidx.room.vo.Field
@@ -86,7 +86,7 @@
 
     @Test
     fun missingEntityAnnotation() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     Source.java(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
index d63633f..6051837 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
@@ -26,7 +26,7 @@
 import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor
 import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
 import androidx.room.compiler.processing.util.Source
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 import com.google.devtools.ksp.processing.SymbolProcessorProvider
 import com.squareup.javapoet.ClassName
@@ -75,7 +75,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(src),
             javacProcessors = listOf(RoomProcessor(), JavacCustomConverter()),
             symbolProcessorProviders =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutFunctionProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutFunctionProcessorTest.kt
index 7857656..023a518 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutFunctionProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutFunctionProcessorTest.kt
@@ -28,6 +28,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
@@ -35,7 +36,6 @@
 import androidx.room.ext.ReactiveStreamsTypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
-import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.shortcut.result.InsertOrUpsertFunctionAdapter
 import androidx.room.testing.context
 import androidx.room.vo.InsertOrUpsertShortcutFunction
@@ -257,13 +257,21 @@
                 @${annotation.java.canonicalName}
                 abstract public void insertUsers(User[] users);
                 """
-        ) { insertionUpsertion, _ ->
+        ) { insertionUpsertion, invocation ->
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
             val param = insertionUpsertion.parameters.first()
             assertThat(param.type.asTypeName())
                 .isEqualTo(
-                    XTypeName.getArrayName(COMMON.USER_TYPE_NAME.copy(nullable = true))
+                    XTypeName.getArrayName(
+                            if (invocation.isKsp2) {
+                                XTypeName.getProducerExtendsName(
+                                    COMMON.USER_TYPE_NAME.copy(nullable = true)
+                                )
+                            } else {
+                                COMMON.USER_TYPE_NAME.copy(nullable = true)
+                            }
+                        )
                         .copy(nullable = true)
                 )
 
@@ -1145,7 +1153,7 @@
                 COMMON.RX3_SINGLE
             )
 
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -1200,7 +1208,7 @@
                 COMMON.GUAVA_ROOM
             )
 
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
index 3df4b7b..af5f768 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
@@ -20,9 +20,9 @@
 import androidx.room.compiler.processing.isTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.SqlParser
 import androidx.room.parser.expansion.ProjectionExpander
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import createVerifierFromEntitiesAndViews
 import org.hamcrest.CoreMatchers.equalTo
@@ -521,7 +521,7 @@
 
     @Test
     fun joinAndAbandonEntity() {
-        runProcessorTestWithK1(sources = ENTITIES) { invocation ->
+        runProcessorTest(sources = ENTITIES) { invocation ->
             val entities =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Entity::class.qualifiedName!!)
@@ -613,7 +613,7 @@
         val extraSource =
             input?.let { listOf(Source.java(name, DATABASE_PREFIX + input)) } ?: emptyList()
         val all = ENTITIES + extraSource
-        return runProcessorTestWithK1(sources = all) { invocation ->
+        return runProcessorTest(sources = all) { invocation ->
             val entities =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Entity::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryFunctionProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryFunctionProcessorTest.kt
index 10f2366..8506249 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryFunctionProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryFunctionProcessorTest.kt
@@ -27,6 +27,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.CommonTypeNames.LIST
 import androidx.room.ext.CommonTypeNames.MUTABLE_LIST
@@ -45,7 +46,6 @@
 import androidx.room.processor.ProcessorErrors.MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED
 import androidx.room.processor.ProcessorErrors.cannotFindQueryResultAdapter
 import androidx.room.processor.ProcessorErrors.mayNeedMapColumn
-import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.query.result.DataClassRowAdapter
 import androidx.room.solver.query.result.DataSourceFactoryQueryResultBinder
 import androidx.room.solver.query.result.ListQueryResultAdapter
@@ -1243,7 +1243,7 @@
             )
         val allOptions =
             mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false") + options
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = additionalSources + commonSources + inputSource,
             options = allOptions
         ) { invocation ->
@@ -1313,7 +1313,7 @@
                 COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
             )
 
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = additionalSources + commonSources + inputSource,
             options = options
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryFunctionProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryFunctionProcessorTest.kt
index 5a851a5..10fe1db 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryFunctionProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryFunctionProcessorTest.kt
@@ -35,10 +35,8 @@
 import androidx.room.ext.RxJava3TypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.RawQueryFunction
-import androidx.sqlite.db.SupportSQLiteQuery
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
@@ -215,15 +213,25 @@
         }
     }
 
-    interface RawQuerySuspendUnitDao {
-        @RawQuery suspend fun foo(query: SupportSQLiteQuery)
-    }
-
     @Test
     fun suspendUnit() {
-        runProcessorTest { invocation ->
-            val daoElement =
-                invocation.processingEnv.requireTypeElement(RawQuerySuspendUnitDao::class)
+        runProcessorTest(
+            sources =
+                listOf(
+                    Source.kotlin(
+                        "RawQuerySuspendUnitDao.kt",
+                        """
+                    import androidx.room.RawQuery
+                    import androidx.sqlite.db.SupportSQLiteQuery
+                    interface RawQuerySuspendUnitDao {
+                        @RawQuery suspend fun foo(query: SupportSQLiteQuery)
+                    }
+                    """
+                            .trimIndent()
+                    )
+                )
+        ) { invocation ->
+            val daoElement = invocation.processingEnv.requireTypeElement("RawQuerySuspendUnitDao")
             val daoFunctionElement = daoElement.getDeclaredMethods().first()
             RawQueryFunctionProcessor(
                     baseContext = invocation.context,
@@ -645,7 +653,7 @@
                 COMMON.IMAGE_FORMAT,
                 COMMON.CONVERTER
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = commonSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -699,7 +707,7 @@
                 COMMON.FLOW,
                 COMMON.GUAVA_ROOM
             )
-        runProcessorTestWithK1(sources = commonSources + inputSource) { invocation ->
+        runProcessorTest(sources = commonSources + inputSource) { invocation ->
             val (owner, functions) =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
index 447dca2..4ffa12b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
@@ -21,7 +21,7 @@
 import androidx.room.RewriteQueriesToDropUnusedColumns
 import androidx.room.compiler.processing.util.CompilationResultSubject
 import androidx.room.compiler.processing.util.Source
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -90,7 +90,7 @@
                 annotateMethod = annotateMethod
             ) + COMMON.USER
 
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = sources,
             createProcessingSteps = { listOf(DatabaseProcessingStep()) },
             options =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
index 8a7c0a3..7e7f14a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
@@ -23,9 +23,9 @@
 import androidx.room.compiler.codegen.XTypeName.Companion.PRIMITIVE_LONG
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CallType
 import androidx.room.vo.DataClass
@@ -339,12 +339,12 @@
             """
                 @PrimaryKey
                 private int id;
-                public void setId(int id) {}
-                public int getId(){ return id; }
                 public int id(){ return id; }
+                public int getId(){ return id; }
+                public void setId(int id) {}
                 """
         ) { _, invocation ->
-            invocation.assertCompilationResult { hasErrorContaining("getId, id") }
+            invocation.assertCompilationResult { hasErrorContaining("id, getId") }
         }
     }
 
@@ -399,12 +399,12 @@
             """
                 @PrimaryKey
                 private int id;
-                public void setId(int id) {}
-                public void id(int id) {}
                 public int getId(){ return id; }
+                public void id(int id) {}
+                public void setId(int id) {}
                 """
         ) { _, invocation ->
-            invocation.assertCompilationResult { hasErrorContaining("setId, id") }
+            invocation.assertCompilationResult { hasErrorContaining("id, setId") }
         }
     }
 
@@ -2633,7 +2633,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(src)) { invocation ->
+        runProcessorTest(sources = listOf(src)) { invocation ->
             val parser =
                 TableEntityProcessor(
                     invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionFunctionProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionFunctionProcessorTest.kt
index 6edea0d..289439b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionFunctionProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionFunctionProcessorTest.kt
@@ -23,6 +23,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
 import androidx.room.ext.KotlinTypeNames.FLOW
 import androidx.room.ext.LifecyclesTypeNames.COMPUTABLE_LIVE_DATA
@@ -30,7 +31,6 @@
 import androidx.room.ext.ReactiveStreamsTypeNames.PUBLISHER
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.TransactionFunction
 import org.hamcrest.CoreMatchers.`is`
@@ -342,7 +342,7 @@
                 COMMON.LISTENABLE_FUTURE,
                 COMMON.FLOW
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = inputSource + otherSources,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false")
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
index b53a255..ae6d959 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
@@ -20,10 +20,10 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.DataClassProcessor
 import androidx.room.processor.FieldProcessor
 import androidx.room.processor.ProcessorErrors
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.DataClass
 import com.google.auto.value.processor.AutoValueProcessor
@@ -113,7 +113,7 @@
                 javacArguments = listOf("-parameters")
             )
         // https://github.com/google/ksp/issues/2033
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = emptyList(),
             classpath = libraryClasspath,
         ) { invocation: XTestInvocation ->
@@ -283,7 +283,7 @@
         val autoValueDataClassSource =
             Source.java(AUTOVALUE_MY_DATA_CLASS.canonicalName, autoValueDataClassCode)
         val all: List<Source> = sources.toList() + dataClassSource + autoValueDataClassSource
-        runProcessorTestWithK1(sources = all, classpath = classpathFiles) { invocation ->
+        runProcessorTest(sources = all, classpath = classpathFiles) { invocation ->
             handler.invoke(
                 DataClassProcessor.createFor(
                         context = invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
index 1887229..b842b28 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
@@ -23,10 +23,10 @@
 import androidx.room.DatabaseProcessingStep
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_STMT_READER
-import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -138,7 +138,7 @@
                 daoAnnotation = daoAnnotation,
                 dbAnnotation = dbAnnotation
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(source),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
index 59f2647..754e591 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
@@ -32,11 +32,11 @@
 import androidx.room.compiler.codegen.compat.XConverters.toString
 import androidx.room.compiler.processing.util.CompilationResultSubject
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomAnnotationTypeNames
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT
-import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -214,7 +214,7 @@
         sources: List<Source>,
         onCompilationResult: (CompilationResultSubject) -> Unit = { it.hasErrorCount(0) }
     ) {
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 sources +
                     CUSTOM_TYPE_JFO +
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
index 8378c4a..3da374a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
@@ -25,12 +25,11 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
 import androidx.room.compiler.processing.util.compiler.compile
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.Context.BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER
 import androidx.room.processor.CustomConverterProcessor
 import androidx.room.processor.DaoProcessor
-import androidx.room.runKspTestWithK1
-import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.types.CustomTypeConverterWrapper
 import androidx.room.solver.types.TypeConverter
 import androidx.room.testing.context
@@ -406,7 +405,7 @@
         """
                     .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(user, day, converters, dao),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -605,7 +604,7 @@
         """
                     .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(source),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -681,7 +680,7 @@
         """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(converters),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -739,7 +738,7 @@
         """
                     .trimIndent()
             )
-        runKspTestWithK1(sources = listOf(source)) { invocation ->
+        runKspTest(sources = listOf(source)) { invocation ->
             val store = invocation.createStore("TimeConverter", "AwesomenessConverter")
             val instantType = invocation.processingEnv.requireType("java.time.Instant")
             val stringType = invocation.processingEnv.requireType("java.lang.String")
@@ -753,7 +752,7 @@
     /** Collect results for conversion from String to our type */
     private fun collectStringConversionResults(vararg selectedConverters: String): String {
         val result = StringBuilder()
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(source),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -801,7 +800,7 @@
     /** Collect results for conversion from an unknown cursor type to our type */
     private fun collectCursorResults(vararg selectedConverters: String): String {
         val result = StringBuilder()
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(source),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 19a60fa..65a13f1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -49,7 +49,6 @@
 import androidx.room.processor.DaoProcessor
 import androidx.room.processor.DaoProcessorTest
 import androidx.room.processor.ProcessorErrors
-import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
 import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider
 import androidx.room.solver.binderprovider.ListenableFuturePagingSourceQueryResultBinderProvider
@@ -142,7 +141,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(entity, converter)) { invocation ->
+        runProcessorTest(sources = listOf(entity, converter)) { invocation ->
             val typeElement =
                 invocation.processingEnv.requireTypeElement("foo.bar.EntityWithOneWayEnum")
             val context = Context(invocation.processingEnv)
@@ -205,7 +204,7 @@
                 """
                     .trimMargin()
             )
-        runProcessorTestWithK1(sources = listOf(enumSrc)) { invocation ->
+        runProcessorTest(sources = listOf(enumSrc)) { invocation ->
             val store =
                 TypeAdapterStore.create(
                     Context(invocation.processingEnv),
@@ -237,7 +236,7 @@
             )
         var results: Map<String, String?> = mutableMapOf()
 
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val typeAdapterStore =
                 TypeAdapterStore.create(
                     context = invocation.context,
@@ -472,7 +471,7 @@
             }
             """
             )
-        runProcessorTestWithK1(sources = listOf(point)) { invocation ->
+        runProcessorTest(sources = listOf(point)) { invocation ->
             val context = Context(invocation.processingEnv)
             val converters =
                 CustomConverterProcessor(
@@ -633,8 +632,7 @@
 
     @Test
     fun testMissingRx2Room() {
-        runProcessorTestWithK1(sources = listOf(COMMON.PUBLISHER, COMMON.RX2_FLOWABLE)) { invocation
-            ->
+        runProcessorTest(sources = listOf(COMMON.PUBLISHER, COMMON.RX2_FLOWABLE)) { invocation ->
             val publisherElement =
                 invocation.processingEnv.requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER)
             assertThat(publisherElement, notNullValue())
@@ -652,8 +650,7 @@
 
     @Test
     fun testMissingRx3Room() {
-        runProcessorTestWithK1(sources = listOf(COMMON.PUBLISHER, COMMON.RX3_FLOWABLE)) { invocation
-            ->
+        runProcessorTest(sources = listOf(COMMON.PUBLISHER, COMMON.RX3_FLOWABLE)) { invocation ->
             val publisherElement =
                 invocation.processingEnv.requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER)
             assertThat(publisherElement, notNullValue())
@@ -692,8 +689,7 @@
 
     @Test
     fun testMissingRoomPagingGuava() {
-        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
-            invocation ->
+        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
             val listenableFuturePagingSourceElement =
                 invocation.processingEnv.requireTypeElement(
                     PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -720,7 +716,7 @@
 
     @Test
     fun testMissingRoomPagingRx2() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
             val rx2PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -741,7 +737,7 @@
 
     @Test
     fun testMissingRoomPagingRx3() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
             val rx3PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -764,7 +760,7 @@
     fun testFindPublisher() {
         listOf(COMMON.RX2_FLOWABLE to COMMON.RX2_ROOM, COMMON.RX3_FLOWABLE to COMMON.RX3_ROOM)
             .forEach { (rxTypeSrc, rxRoomSrc) ->
-                runProcessorTestWithK1(
+                runProcessorTest(
                     sources = listOf(rxTypeSrc, rxRoomSrc),
                     classpath =
                         compileFiles(
@@ -803,7 +799,7 @@
                 Triple(COMMON.RX3_FLOWABLE, COMMON.RX3_ROOM, RxJava3TypeNames.FLOWABLE)
             )
             .forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) ->
-                runProcessorTestWithK1(
+                runProcessorTest(
                     sources = listOf(rxTypeSrc, rxRoomSrc),
                     classpath =
                         compileFiles(
@@ -838,7 +834,7 @@
                 Triple(COMMON.RX3_OBSERVABLE, COMMON.RX3_ROOM, RxJava3TypeNames.OBSERVABLE)
             )
             .forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) ->
-                runProcessorTestWithK1(
+                runProcessorTest(
                     sources = listOf(rxTypeSrc, rxRoomSrc),
                     classpath =
                         compileFiles(
@@ -876,7 +872,7 @@
                 Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
                     val single = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(single, notNullValue())
                     assertThat(
@@ -895,7 +891,7 @@
                 Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
                     val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                         RxCallableInsertOrUpsertFunctionBinderProvider.getAll(invocation.context)
@@ -913,7 +909,7 @@
                 Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
                     val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                         RxCallableInsertOrUpsertFunctionBinderProvider.getAll(invocation.context)
@@ -926,7 +922,7 @@
 
     @Test
     fun testFindInsertListenableFuture() {
-        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
             val future =
                 invocation.processingEnv.requireTypeElement(
                     GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -941,7 +937,7 @@
 
     @Test
     fun testFindDeleteOrUpdateSingle() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX2_SINGLE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX2_SINGLE)) { invocation ->
             val single = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.SINGLE)
             assertThat(single, notNullValue())
             assertThat(
@@ -955,7 +951,7 @@
 
     @Test
     fun testFindDeleteOrUpdateMaybe() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX2_MAYBE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX2_MAYBE)) { invocation ->
             val maybe = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.MAYBE)
             assertThat(maybe, notNullValue())
             assertThat(
@@ -969,7 +965,7 @@
 
     @Test
     fun testFindDeleteOrUpdateCompletable() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX2_COMPLETABLE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX2_COMPLETABLE)) { invocation ->
             val completable =
                 invocation.processingEnv.requireTypeElement(RxJava2TypeNames.COMPLETABLE)
             assertThat(completable, notNullValue())
@@ -984,7 +980,7 @@
 
     @Test
     fun testFindDeleteOrUpdateListenableFuture() {
-        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
             val future =
                 invocation.processingEnv.requireTypeElement(
                     GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -1005,7 +1001,7 @@
                 Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
                     val single = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(single).isNotNull()
                     assertThat(
@@ -1026,7 +1022,7 @@
                 Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
                     val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                             RxCallableInsertOrUpsertFunctionBinderProvider.getAll(
@@ -1046,7 +1042,7 @@
                 Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
                     val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                             RxCallableInsertOrUpsertFunctionBinderProvider.getAll(
@@ -1061,7 +1057,7 @@
 
     @Test
     fun testFindUpsertListenableFuture() {
-        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
             val future =
                 invocation.processingEnv.requireTypeElement(
                     GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -1076,7 +1072,7 @@
 
     @Test
     fun testFindLiveData() {
-        runProcessorTestWithK1(sources = listOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
+        runProcessorTest(sources = listOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
             invocation ->
             val liveData =
                 invocation.processingEnv.requireTypeElement(LifecyclesTypeNames.LIVE_DATA)
@@ -1090,7 +1086,7 @@
 
     @Test
     fun findPagingSourceIntKey() {
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = listOf(COMMON.LIMIT_OFFSET_PAGING_SOURCE),
         ) { invocation ->
             val pagingSourceElement =
@@ -1234,8 +1230,7 @@
 
     @Test
     fun findListenableFuturePagingSourceJavaCollectionValue() {
-        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
-            invocation ->
+        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
             val listenableFuturePagingSourceElement =
                 invocation.processingEnv.requireTypeElement(
                     PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -1263,8 +1258,7 @@
 
     @Test
     fun findListenableFutureKotlinCollectionValue() {
-        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
-            invocation ->
+        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
             val listenableFuturePagingSourceElement =
                 invocation.processingEnv.requireTypeElement(
                     PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -1292,7 +1286,7 @@
 
     @Test
     fun findRx2PagingSourceJavaCollectionValue() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
             val rx2PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1318,7 +1312,7 @@
 
     @Test
     fun findRx2PagingSourceKotlinCollectionValue() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
             val rx2PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1344,7 +1338,7 @@
 
     @Test
     fun findRx3PagingSourceJavaCollectionValue() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
             val rx3PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1370,7 +1364,7 @@
 
     @Test
     fun findRx3PagingSourceKotlinCollectionValue() {
-        runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
             val rx3PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1410,7 +1404,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     inputSource,
@@ -1477,7 +1471,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     inputSource,
@@ -1540,7 +1534,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     inputSource,
@@ -1599,7 +1593,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     inputSource,
@@ -1675,7 +1669,7 @@
 
     @Test
     fun findDataSourceFactory() {
-        runProcessorTestWithK1(sources = listOf(COMMON.DATA_SOURCE_FACTORY)) { invocation ->
+        runProcessorTest(sources = listOf(COMMON.DATA_SOURCE_FACTORY)) { invocation ->
             val pagedListProvider =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY)
             assertThat(pagedListProvider, notNullValue())
@@ -1758,7 +1752,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val converters =
                 CustomConverterProcessor(
                         context = invocation.context,
@@ -1894,7 +1888,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     classExtendsClassWithEqualsAndHashcodeFunctions,
@@ -1937,7 +1931,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources =
                 listOf(
                     inputSource,
@@ -1969,7 +1963,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val subjectTypeElement = invocation.processingEnv.requireTypeElement("Subject")
 
             subjectTypeElement.getDeclaredFields().forEach {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
index d67ad7d..c02a5de 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
@@ -20,7 +20,7 @@
 import androidx.room.compiler.processing.XVariableElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
@@ -101,6 +101,6 @@
     }
 
     private fun runTest(handler: XTestInvocation.() -> Unit) {
-        runProcessorTestWithK1(sources = listOf(TEST_OBJECT)) { it.apply { handler() } }
+        runProcessorTest(sources = listOf(TEST_OBJECT)) { it.apply { handler() } }
     }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
index 12ca821..10e445c 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
@@ -19,8 +19,8 @@
 import androidx.kruth.assertThat
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.CustomConverterProcessor
-import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.types.CompositeTypeConverter
 import androidx.room.solver.types.CustomTypeConverterWrapper
 import androidx.room.solver.types.TypeConverter
@@ -65,7 +65,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val convertersElm = invocation.processingEnv.requireTypeElement("MyConverters")
             val converters = CustomConverterProcessor(invocation.context, convertersElm).process()
             val store =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
index 23c72a0..0f6d9ff 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
@@ -23,10 +23,10 @@
 import androidx.room.compiler.codegen.compat.XConverters.toString
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames.ROOM_SQL_QUERY
 import androidx.room.ext.RoomTypeNames.STRING_UTIL
 import androidx.room.processor.QueryFunctionProcessor
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.writer.QueryWriter
 import org.junit.Test
@@ -362,7 +362,7 @@
     fun singleQueryMethod(vararg input: String, handler: (Boolean, QueryWriter) -> Unit) {
         val source =
             Source.java("foo.bar.MyClass", DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX)
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+        runProcessorTest(sources = listOf(source)) { invocation ->
             val (owner, methods) =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
index cdb11ac..521e634 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
@@ -19,7 +19,7 @@
 import androidx.kruth.assertThat
 import androidx.room.compiler.processing.util.CompilationTestCapabilities
 import androidx.room.compiler.processing.util.Source
-import androidx.room.runProcessorTestWithK1
+import androidx.room.compiler.processing.util.runProcessorTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -58,7 +58,7 @@
             }
 
         var runCount = 0
-        runProcessorTestWithK1(sources = listOf(source)) {
+        runProcessorTest(sources = listOf(source)) {
             assertThat(it.processingEnv.findTypeElement("foo.bar.MyClass")).isNotNull()
             runCount++
         }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
index db09881..569e32f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
@@ -21,9 +21,9 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runJavaProcessorTest
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.migration.bundle.FieldBundle
 import androidx.room.processor.Context
-import androidx.room.runKspTestWithK1
 import androidx.room.util.SchemaDiffResult
 import androidx.room.vo.AutoMigration
 import loadTestSource
@@ -88,7 +88,7 @@
                     )
             }
 
-        runProcessorTestWithK1(sources = listOf(specSource)) { invocation ->
+        runProcessorTest(sources = listOf(specSource)) { invocation ->
             val autoMigrationResultWithNewAddedColumn =
                 AutoMigration(
                     from = 1,
@@ -172,7 +172,7 @@
                     )
             }
 
-        runProcessorTestWithK1(listOf(specSource)) { invocation ->
+        runProcessorTest(listOf(specSource)) { invocation ->
             val autoMigrationResultWithNewAddedColumn =
                 AutoMigration(
                     from = 1,
@@ -264,7 +264,7 @@
                     )
             }
 
-        runProcessorTestWithK1(listOf(specSource)) { invocation ->
+        runProcessorTest(listOf(specSource)) { invocation ->
             val autoMigrationResultWithNewAddedColumn =
                 AutoMigration(
                     from = 1,
@@ -320,7 +320,7 @@
         }
     }
 
-    private fun runProcessorTestWithK1(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+    private fun runProcessorTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
         when (codeLanguage) {
             CodeLanguage.JAVA ->
                 runJavaProcessorTest(
@@ -330,7 +330,7 @@
                     handler = handler
                 )
             CodeLanguage.KOTLIN ->
-                runKspTestWithK1(
+                runKspTest(
                     sources = sources + kotlinDatabaseSource,
                     options =
                         mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
index 279b6df..6e22c5c 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
@@ -17,11 +17,11 @@
 package androidx.room.writer
 
 import androidx.room.DatabaseProcessingStep
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.processor.Context
-import androidx.room.runKspTestWithK1
 import java.io.File
 import loadTestSource
 import writeTestSource
@@ -66,22 +66,17 @@
             }
             handler.invoke(it)
         }
-        if (withKsp2) {
-            runKspTest(
-                sources = sources,
-                classpath = compiledFiles,
-                options = options,
-                kotlincArguments = kotlincArguments,
-                handler = invocationHandler
-            )
-        } else {
-            runKspTestWithK1(
-                sources = sources,
-                classpath = compiledFiles,
-                options = options,
-                kotlincArguments = kotlincArguments,
-                handler = invocationHandler
-            )
-        }
+        runKspTest(
+            sources = sources,
+            classpath = compiledFiles,
+            options = options,
+            kotlincArguments =
+                if (!withKsp2) {
+                    KOTLINC_LANGUAGE_1_9_ARGS
+                } else {
+                    emptyList<String>()
+                } + kotlincArguments,
+            handler = invocationHandler
+        )
     }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
index 55272a9..d4b04df 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -1603,6 +1603,9 @@
                 @Query("SELECT * FROM Artist JOIN Song ON Artist.artistId = Song.artistKey")
                 fun getArtistWithSongs(): Map<Artist, List<Song>>
 
+                @Query("SELECT * FROM Artist JOIN Song ON Artist.artistId = Song.artistKey")
+                fun getArtistWithMutableSongs(): Map<Artist, MutableList<Song>>
+
                 @Suppress("DEPRECATION") // For @MapInfo
                 @MapInfo(valueColumn = "songCount")
                 @Query(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
index 4f40baa..76c0f69 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
@@ -23,9 +23,9 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.DaoProcessor
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import com.google.testing.junit.testparameterinjector.TestParameter
 import com.google.testing.junit.testparameterinjector.TestParameterInjector
@@ -150,14 +150,14 @@
                     COMMON.LIMIT_OFFSET_PAGING_SOURCE
                 )
             )
-        runProcessorTestWithK1(
+        runProcessorTest(
             sources = sources,
             classpath = libs,
             kotlincArguments = listOf("-jvm-target=11")
         ) { invocation ->
             if (invocation.isKsp && !javaLambdaSyntaxAvailable) {
                 // Skip KSP backend without lambda syntax, it is a nonsensical combination.
-                return@runProcessorTestWithK1
+                return@runProcessorTest
             }
             val dao =
                 invocation.roundEnv
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
index 3a6c7f7..2ef68b0 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
@@ -19,8 +19,8 @@
 import androidx.room.DatabaseProcessingStep
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.processor.Context
-import androidx.room.runKspTestWithK1
 import loadTestSource
 import org.junit.Rule
 import org.junit.Test
@@ -122,7 +122,7 @@
         expectedFilePath: String,
         handler: (XTestInvocation) -> Unit = {}
     ) {
-        runKspTestWithK1(
+        runKspTest(
             sources = sources,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
index 26f7d0b..5e9aa20 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
@@ -19,8 +19,8 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.DatabaseProcessor
-import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.Database
 import org.hamcrest.CoreMatchers.`is`
@@ -219,7 +219,7 @@
             }
             """
             )
-        runProcessorTestWithK1(sources = sources + databaseCode) { invocation ->
+        runProcessorTest(sources = sources + databaseCode) { invocation ->
             val db =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt
index cae7c1a..f7f63f7 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt
@@ -98,6 +98,46 @@
     }
   }
 
+  public override fun getArtistWithMutableSongs(): Map<Artist, MutableList<Song>> {
+    val _sql: String = "SELECT * FROM Artist JOIN Song ON Artist.artistId = Song.artistKey"
+    return performBlocking(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        val _columnIndexOfArtistId: Int = getColumnIndexOrThrow(_stmt, "artistId")
+        val _columnIndexOfSongId: Int = getColumnIndexOrThrow(_stmt, "songId")
+        val _columnIndexOfArtistKey: Int = getColumnIndexOrThrow(_stmt, "artistKey")
+        val _result: MutableMap<Artist, MutableList<Song>> =
+            LinkedHashMap<Artist, MutableList<Song>>()
+        while (_stmt.step()) {
+          val _key: Artist
+          val _tmpArtistId: String
+          _tmpArtistId = _stmt.getText(_columnIndexOfArtistId)
+          _key = Artist(_tmpArtistId)
+          val _values: MutableList<Song>
+          if (_result.containsKey(_key)) {
+            _values = _result.getValue(_key)
+          } else {
+            _values = mutableListOf()
+            _result.put(_key, _values)
+          }
+          if (_stmt.isNull(_columnIndexOfSongId) && _stmt.isNull(_columnIndexOfArtistKey)) {
+            continue
+          }
+          val _value: Song
+          val _tmpSongId: String
+          _tmpSongId = _stmt.getText(_columnIndexOfSongId)
+          val _tmpArtistKey: String
+          _tmpArtistKey = _stmt.getText(_columnIndexOfArtistKey)
+          _value = Song(_tmpSongId,_tmpArtistKey)
+          _values.add(_value)
+        }
+        _result
+      } finally {
+        _stmt.close()
+      }
+    }
+  }
+
   public override fun getArtistSongCount(): Map<Artist, Int> {
     val _sql: String =
         "SELECT Artist.*, COUNT(songId) as songCount FROM Artist JOIN Song ON Artist.artistId = Song.artistKey GROUP BY artistId"
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index df92e99..da3faa1 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -530,17 +530,25 @@
   @androidx.wear.compose.foundation.lazy.TransformingLazyColumnScopeMarker public sealed interface TransformingLazyColumnItemScope {
     method @androidx.compose.runtime.Composable public default void TransformExclusion(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScope,kotlin.Unit> content);
     method public androidx.compose.ui.Modifier animateItem(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeInSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? placementSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeOutSpec);
-    method public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? getScrollProgress(androidx.compose.ui.graphics.drawscope.DrawScope);
-    method public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? getScrollProgress(androidx.compose.ui.graphics.GraphicsLayerScope);
+    method public long getScrollProgress(androidx.compose.ui.graphics.drawscope.DrawScope);
+    method public long getScrollProgress(androidx.compose.ui.graphics.GraphicsLayerScope);
     method public androidx.compose.ui.Modifier transformedHeight(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress,java.lang.Integer> heightProvider);
-    property public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? scrollProgress;
-    property public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? scrollProgress;
+    property public long scrollProgress;
+    property public long scrollProgress;
   }
 
   @kotlin.jvm.JvmInline public final value class TransformingLazyColumnItemScrollProgress {
     ctor public TransformingLazyColumnItemScrollProgress(float topOffsetFraction, float bottomOffsetFraction);
     property public final float bottomOffsetFraction;
+    property public final boolean isSpecified;
+    property public final boolean isUnspecified;
     property public final float topOffsetFraction;
+    field public static final androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress.Companion Companion;
+  }
+
+  public static final class TransformingLazyColumnItemScrollProgress.Companion {
+    method public long getUnspecified();
+    property public final long Unspecified;
   }
 
   public final class TransformingLazyColumnKt {
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index df92e99..da3faa1 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -530,17 +530,25 @@
   @androidx.wear.compose.foundation.lazy.TransformingLazyColumnScopeMarker public sealed interface TransformingLazyColumnItemScope {
     method @androidx.compose.runtime.Composable public default void TransformExclusion(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScope,kotlin.Unit> content);
     method public androidx.compose.ui.Modifier animateItem(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeInSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? placementSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeOutSpec);
-    method public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? getScrollProgress(androidx.compose.ui.graphics.drawscope.DrawScope);
-    method public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? getScrollProgress(androidx.compose.ui.graphics.GraphicsLayerScope);
+    method public long getScrollProgress(androidx.compose.ui.graphics.drawscope.DrawScope);
+    method public long getScrollProgress(androidx.compose.ui.graphics.GraphicsLayerScope);
     method public androidx.compose.ui.Modifier transformedHeight(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress,java.lang.Integer> heightProvider);
-    property public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? scrollProgress;
-    property public androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress? scrollProgress;
+    property public long scrollProgress;
+    property public long scrollProgress;
   }
 
   @kotlin.jvm.JvmInline public final value class TransformingLazyColumnItemScrollProgress {
     ctor public TransformingLazyColumnItemScrollProgress(float topOffsetFraction, float bottomOffsetFraction);
     property public final float bottomOffsetFraction;
+    property public final boolean isSpecified;
+    property public final boolean isUnspecified;
     property public final float topOffsetFraction;
+    field public static final androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress.Companion Companion;
+  }
+
+  public static final class TransformingLazyColumnItemScrollProgress.Companion {
+    method public long getUnspecified();
+    property public final long Unspecified;
   }
 
   public final class TransformingLazyColumnKt {
diff --git a/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/TransformingLazyColumnSample.kt b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/TransformingLazyColumnSample.kt
index 3a1ce02..5dd650f 100644
--- a/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/TransformingLazyColumnSample.kt
+++ b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/TransformingLazyColumnSample.kt
@@ -106,34 +106,36 @@
             Text(
                 alphabet[index],
                 modifier =
-                    Modifier.transformedHeight { originalHeight, scrollProgression ->
-                            if (scrollProgression.topOffsetFraction < 0f)
-                                (originalHeight * scrollProgression.bottomOffsetFraction /
-                                        (scrollProgression.bottomOffsetFraction -
-                                            scrollProgression.topOffsetFraction))
+                    Modifier.transformedHeight { measuredHeight, scrollProgress ->
+                            if (scrollProgress.topOffsetFraction < 0f)
+                                (measuredHeight * scrollProgress.bottomOffsetFraction /
+                                        (scrollProgress.bottomOffsetFraction -
+                                            scrollProgress.topOffsetFraction))
                                     .roundToInt()
-                            else originalHeight
+                            else measuredHeight
                         }
                         .graphicsLayer {
-                            val itemProgression = scrollProgress ?: return@graphicsLayer
-                            rotationY =
-                                -180f +
-                                    (itemProgression.topOffsetFraction +
-                                        itemProgression.bottomOffsetFraction) * 180f
-                            val scale =
-                                (itemProgression.bottomOffsetFraction -
-                                    max(itemProgression.topOffsetFraction, 0f)) /
-                                    (itemProgression.bottomOffsetFraction -
-                                        itemProgression.topOffsetFraction)
-                            scaleY = scale
-                            translationY = size.height * (scale - 1f) / 2f
+                            with(scrollProgress) {
+                                if (isUnspecified) {
+                                    return@graphicsLayer
+                                }
+                                rotationY =
+                                    -180f + (topOffsetFraction + bottomOffsetFraction) * 180f
+                                val scale =
+                                    (bottomOffsetFraction - max(topOffsetFraction, 0f)) /
+                                        (bottomOffsetFraction - topOffsetFraction)
+                                scaleY = scale
+                                translationY = size.height * (scale - 1f) / 2f
+                            }
                         }
                         .drawBehind {
-                            val colorProgress =
-                                scrollProgress?.let {
-                                    (it.topOffsetFraction + it.bottomOffsetFraction) / 2f
-                                } ?: 0f
-                            drawCircle(rainbowColor(colorProgress))
+                            with(scrollProgress) {
+                                if (isUnspecified) {
+                                    return@drawBehind
+                                }
+                                val colorProgress = (topOffsetFraction + bottomOffsetFraction) / 2f
+                                drawCircle(rainbowColor(colorProgress))
+                            }
                         }
                         .padding(20.dp)
             )
@@ -180,20 +182,24 @@
                     itemScope?.let {
                         with(it) {
                             Modifier.graphicsLayer {
-                                    val itemProgression = scrollProgress ?: return@graphicsLayer
-                                    val scale =
-                                        (itemProgression.bottomOffsetFraction -
-                                            max(itemProgression.topOffsetFraction, 0f)) /
-                                            (itemProgression.bottomOffsetFraction -
-                                                itemProgression.topOffsetFraction)
-                                    scaleX = scale
+                                    with(scrollProgress) {
+                                        if (isUnspecified) {
+                                            return@graphicsLayer
+                                        }
+                                        scaleX =
+                                            (bottomOffsetFraction - max(topOffsetFraction, 0f)) /
+                                                (bottomOffsetFraction - topOffsetFraction)
+                                    }
                                 }
                                 .drawBehind {
-                                    val colorProgress =
-                                        scrollProgress?.let {
-                                            (it.topOffsetFraction + it.bottomOffsetFraction) / 2f
-                                        } ?: 0f
-                                    drawRect(rainbowColor(colorProgress))
+                                    with(scrollProgress) {
+                                        if (isUnspecified) {
+                                            return@drawBehind
+                                        }
+                                        val colorProgress =
+                                            (topOffsetFraction + bottomOffsetFraction) / 2f
+                                        drawRect(rainbowColor(colorProgress))
+                                    }
                                 }
                         }
                     } ?: Modifier.background(Color.Green)
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnItemScrollProgressTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnItemScrollProgressTest.kt
index 375583f..03cc9f1 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnItemScrollProgressTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnItemScrollProgressTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -79,4 +80,27 @@
         assertEquals(progress.topOffsetFraction, Float.NEGATIVE_INFINITY)
         assertEquals(progress.bottomOffsetFraction, Float.POSITIVE_INFINITY)
     }
+
+    @Test
+    fun isSpecifiedIsReportedCorrectly() {
+        val progress =
+            TransformingLazyColumnItemScrollProgress(
+                topOffsetFraction = 0f,
+                bottomOffsetFraction = 0f
+            )
+        assertTrue(progress.isSpecified)
+    }
+
+    @Test
+    fun isUnspecifiedIsReportedCorrectly() {
+        val progress = TransformingLazyColumnItemScrollProgress.Unspecified
+        assertTrue(progress.isUnspecified)
+    }
+
+    @Test
+    fun unpackingUnspecifiedValues() {
+        val progress = TransformingLazyColumnItemScrollProgress.Unspecified
+        assertEquals(progress.topOffsetFraction, Float.NaN)
+        assertEquals(progress.bottomOffsetFraction, Float.NaN)
+    }
 }
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnDsl.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnDsl.kt
index cb97b36..34e2242 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnDsl.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnDsl.kt
@@ -40,15 +40,17 @@
 public sealed interface TransformingLazyColumnItemScope {
     /**
      * Scroll progress of the item before height transformation is applied using
-     * [Modifier.transformedHeight]. Is null for the item that is off screen.
+     * [Modifier.transformedHeight]. Is [TransformingLazyColumnItemScrollProgress.Unspecified] for
+     * the item that is off screen.
      */
-    public val DrawScope.scrollProgress: TransformingLazyColumnItemScrollProgress?
+    public val DrawScope.scrollProgress: TransformingLazyColumnItemScrollProgress
 
     /**
      * Scroll progress of the item before height transformation is applied using
-     * [Modifier.transformedHeight]. Is null for the item that is off screen.
+     * [Modifier.transformedHeight]. Is [TransformingLazyColumnItemScrollProgress.Unspecified] for
+     * the item that is off screen.
      */
-    public val GraphicsLayerScope.scrollProgress: TransformingLazyColumnItemScrollProgress?
+    public val GraphicsLayerScope.scrollProgress: TransformingLazyColumnItemScrollProgress
 
     /**
      * Applies the new height of the item depending on its scroll progress and measured height.
@@ -209,13 +211,15 @@
     val reduceMotionEnabled: Boolean
 ) : TransformingLazyColumnItemScope {
 
-    private val _scrollProgress: TransformingLazyColumnItemScrollProgress?
-        get() = state.layoutInfo.visibleItems.fastFirstOrNull { it.index == index }?.scrollProgress
+    private val _scrollProgress: TransformingLazyColumnItemScrollProgress
+        get() =
+            state.layoutInfo.visibleItems.fastFirstOrNull { it.index == index }?.scrollProgress
+                ?: TransformingLazyColumnItemScrollProgress.Unspecified
 
-    override val DrawScope.scrollProgress: TransformingLazyColumnItemScrollProgress?
+    override val DrawScope.scrollProgress: TransformingLazyColumnItemScrollProgress
         get() = _scrollProgress
 
-    override val GraphicsLayerScope.scrollProgress: TransformingLazyColumnItemScrollProgress?
+    override val GraphicsLayerScope.scrollProgress: TransformingLazyColumnItemScrollProgress
         get() = _scrollProgress
 
     override fun Modifier.transformedHeight(
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnLayoutInfo.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnLayoutInfo.kt
index f4e4cc2..a0f0c18 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnLayoutInfo.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnLayoutInfo.kt
@@ -33,7 +33,8 @@
      * of the height of the list container. Is within (0, 1) when item is inside the screen and
      * could be negative if the top of the item is off the screen. Value is calculated from the top
      * of the container. This value is calculated before any height modifications are applied (using
-     * [TransformingLazyColumnItemScope.transformedHeight] modifier).
+     * [TransformingLazyColumnItemScope.transformedHeight] modifier). This returns [Float.NaN] if
+     * the progress was [Unspecified].
      */
     public val topOffsetFraction: Float
         get() = unpackFloat1(packedValue)
@@ -43,11 +44,20 @@
      * fraction of the height of the list container. Is within (0, 1) when item is inside the screen
      * and could exceed 1 when the bottom of item is off the screen. Value is calculated from the
      * top of the container. This value is calculated before any height modifications are applied
-     * (using [TransformingLazyColumnItemScope.transformedHeight] modifier).
+     * (using [TransformingLazyColumnItemScope.transformedHeight] modifier). This returns
+     * [Float.NaN] if the progress was [Unspecified].
      */
     public val bottomOffsetFraction: Float
         get() = unpackFloat2(packedValue)
 
+    /** `true` when this is [TransformingLazyColumnItemScrollProgress.Unspecified]. */
+    public val isUnspecified: Boolean
+        get() = packedValue == UnspecifiedPackedFloats
+
+    /** `false` when this is [TransformingLazyColumnItemScrollProgress.Unspecified]. */
+    public val isSpecified: Boolean
+        get() = packedValue != UnspecifiedPackedFloats
+
     /**
      * Constructs a [TransformingLazyColumnItemScrollProgress] with two offset fraction [Float]
      * values.
@@ -62,12 +72,13 @@
         bottomOffsetFraction: Float
     ) : this(packFloats(topOffsetFraction, bottomOffsetFraction))
 
-    internal companion object {
+    public companion object {
         /**
          * Represents an unspecified [TransformingLazyColumnItemScrollProgress] value, usually a
          * replacement for `null` when a primitive value is desired.
          */
-        val Unspecified = TransformingLazyColumnItemScrollProgress(UnspecifiedPackedFloats)
+        public val Unspecified: TransformingLazyColumnItemScrollProgress =
+            TransformingLazyColumnItemScrollProgress(UnspecifiedPackedFloats)
 
         internal fun bottomItemScrollProgress(
             offset: Int,
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 71a9e44..9ddffa6 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -799,18 +799,24 @@
   }
 
   public final class OpenOnPhoneDialogDefaults {
+    method @androidx.compose.runtime.Composable public void Icon(optional androidx.compose.ui.Modifier modifier, optional String contentDescription);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors(optional long iconColor, optional long iconContainerColor, optional long progressIndicatorColor, optional long progressTrackColor, optional long textColor);
-    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(optional String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
-    method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getOpenOnPhoneIcon();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.CurvedTextStyle getCurvedTextStyle();
+    method @androidx.compose.runtime.Composable public String getIconContentDescription();
+    method @androidx.compose.runtime.Composable public String getText();
     property public static final long DurationMillis;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> OpenOnPhoneIcon;
+    property @androidx.compose.runtime.Composable public final androidx.wear.compose.foundation.CurvedTextStyle curvedTextStyle;
+    property @androidx.compose.runtime.Composable public final String iconContentDescription;
+    property @androidx.compose.runtime.Composable public final String text;
     field public static final long DurationMillis = 4000L; // 0xfa0L
     field public static final androidx.wear.compose.material3.OpenOnPhoneDialogDefaults INSTANCE;
   }
 
   public final class OpenOnPhoneDialogKt {
-    method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean visible, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialogContent(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, long durationMillis, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method public static void openOnPhoneCurvedText(androidx.wear.compose.foundation.CurvedScope, String text, androidx.wear.compose.foundation.CurvedTextStyle style);
   }
 
   public final class PageIndicatorDefaults {
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 71a9e44..9ddffa6 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -799,18 +799,24 @@
   }
 
   public final class OpenOnPhoneDialogDefaults {
+    method @androidx.compose.runtime.Composable public void Icon(optional androidx.compose.ui.Modifier modifier, optional String contentDescription);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors(optional long iconColor, optional long iconContainerColor, optional long progressIndicatorColor, optional long progressTrackColor, optional long textColor);
-    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(optional String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
-    method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getOpenOnPhoneIcon();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.CurvedTextStyle getCurvedTextStyle();
+    method @androidx.compose.runtime.Composable public String getIconContentDescription();
+    method @androidx.compose.runtime.Composable public String getText();
     property public static final long DurationMillis;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> OpenOnPhoneIcon;
+    property @androidx.compose.runtime.Composable public final androidx.wear.compose.foundation.CurvedTextStyle curvedTextStyle;
+    property @androidx.compose.runtime.Composable public final String iconContentDescription;
+    property @androidx.compose.runtime.Composable public final String text;
     field public static final long DurationMillis = 4000L; // 0xfa0L
     field public static final androidx.wear.compose.material3.OpenOnPhoneDialogDefaults INSTANCE;
   }
 
   public final class OpenOnPhoneDialogKt {
-    method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialog(boolean visible, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, optional androidx.compose.ui.window.DialogProperties properties, optional long durationMillis, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void OpenOnPhoneDialogContent(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? curvedText, long durationMillis, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.OpenOnPhoneDialogColors colors, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method public static void openOnPhoneCurvedText(androidx.wear.compose.foundation.CurvedScope, String text, androidx.wear.compose.foundation.CurvedTextStyle style);
   }
 
   public final class PageIndicatorDefaults {
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/OpenOnPhoneDialogDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/OpenOnPhoneDialogDemo.kt
index ab628e5..195a85a 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/OpenOnPhoneDialogDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/OpenOnPhoneDialogDemo.kt
@@ -31,6 +31,7 @@
 import androidx.wear.compose.material3.OpenOnPhoneDialog
 import androidx.wear.compose.material3.OpenOnPhoneDialogDefaults
 import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.openOnPhoneCurvedText
 import androidx.wear.compose.material3.samples.OpenOnPhoneDialogSample
 
 val OpenOnPhoneDialogDemos =
@@ -42,38 +43,40 @@
 
 @Composable
 fun OpenOnPhoneDialogWithCustomText() {
-    var showConfirmation by remember { mutableStateOf(false) }
+    var visible by remember { mutableStateOf(false) }
 
     Box(Modifier.fillMaxSize()) {
         FilledTonalButton(
             modifier = Modifier.align(Alignment.Center),
-            onClick = { showConfirmation = true },
+            onClick = { visible = true },
             label = { Text("Open on phone") }
         )
     }
 
+    val curvedTextStyle = OpenOnPhoneDialogDefaults.curvedTextStyle
     OpenOnPhoneDialog(
-        show = showConfirmation,
-        onDismissRequest = { showConfirmation = false },
-        curvedText = OpenOnPhoneDialogDefaults.curvedText("Custom text")
+        visible = visible,
+        onDismissRequest = { visible = false },
+        curvedText = { openOnPhoneCurvedText(text = "Custom text", style = curvedTextStyle) }
     )
 }
 
 @Composable
 fun OpenOnPhoneDialogWithCustomColors() {
-    var showConfirmation by remember { mutableStateOf(false) }
+    var visible by remember { mutableStateOf(false) }
 
     Box(Modifier.fillMaxSize()) {
         FilledTonalButton(
             modifier = Modifier.align(Alignment.Center),
-            onClick = { showConfirmation = true },
+            onClick = { visible = true },
             label = { Text("Open on phone") }
         )
     }
 
+    val curvedTextStyle = OpenOnPhoneDialogDefaults.curvedTextStyle
     OpenOnPhoneDialog(
-        show = showConfirmation,
-        onDismissRequest = { showConfirmation = false },
+        visible = visible,
+        onDismissRequest = { visible = false },
         colors =
             OpenOnPhoneDialogDefaults.colors(
                 iconColor = MaterialTheme.colorScheme.tertiary,
@@ -81,6 +84,7 @@
                 progressIndicatorColor = MaterialTheme.colorScheme.tertiary,
                 progressTrackColor = MaterialTheme.colorScheme.onTertiary,
                 textColor = MaterialTheme.colorScheme.onSurfaceVariant
-            )
+            ),
+        curvedText = { openOnPhoneCurvedText(text = "Custom colors", style = curvedTextStyle) }
     )
 }
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/OpenOnPhoneDialogBenchmark.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/OpenOnPhoneDialogBenchmark.kt
index e79a381..e738a09 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/OpenOnPhoneDialogBenchmark.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/OpenOnPhoneDialogBenchmark.kt
@@ -33,7 +33,9 @@
 import androidx.test.uiautomator.Until
 import androidx.wear.compose.material3.Button
 import androidx.wear.compose.material3.OpenOnPhoneDialog
+import androidx.wear.compose.material3.OpenOnPhoneDialogDefaults
 import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.openOnPhoneCurvedText
 
 object OpenOnPhoneDialogBenchmark : MacrobenchmarkScreen {
     override val content: @Composable (BoxScope.() -> Unit)
@@ -51,10 +53,13 @@
                     Text("Open")
                 }
             }
+            val text = OpenOnPhoneDialogDefaults.text
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
-                show = showDialog.value,
+                visible = showDialog.value,
                 onDismissRequest = { showDialog.value = false },
                 durationMillis = 2000,
+                curvedText = { openOnPhoneCurvedText(text = text, style = style) }
             )
         }
 
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt
index 2530fe3..849652f 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt
@@ -39,6 +39,7 @@
 import androidx.wear.compose.material3.Text
 import androidx.wear.compose.material3.macrobenchmark.common.FIND_OBJECT_TIMEOUT_MS
 import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
+import androidx.wear.compose.material3.openOnPhoneCurvedText
 
 val OpenOnPhoneDialogScreen =
     object : MacrobenchmarkScreen {
@@ -61,9 +62,12 @@
                     val test = ""
                     test.let { ExcludePaths.none { path -> it.contains(path) } }
 
+                    val text = OpenOnPhoneDialogDefaults.text
+                    val style = OpenOnPhoneDialogDefaults.curvedTextStyle
                     OpenOnPhoneDialog(
-                        show = showConfirmation,
+                        visible = showConfirmation,
                         onDismissRequest = { showConfirmation = false },
+                        curvedText = { openOnPhoneCurvedText(text = text, style = style) }
                     )
                 }
             }
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/OpenOnPhoneDialogSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/OpenOnPhoneDialogSample.kt
index 741132e..6bbc6b5 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/OpenOnPhoneDialogSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/OpenOnPhoneDialogSample.kt
@@ -28,7 +28,9 @@
 import androidx.compose.ui.Modifier
 import androidx.wear.compose.material3.FilledTonalButton
 import androidx.wear.compose.material3.OpenOnPhoneDialog
+import androidx.wear.compose.material3.OpenOnPhoneDialogDefaults
 import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.openOnPhoneCurvedText
 
 @Sampled
 @Composable
@@ -43,8 +45,11 @@
         )
     }
 
+    val text = OpenOnPhoneDialogDefaults.text
+    val style = OpenOnPhoneDialogDefaults.curvedTextStyle
     OpenOnPhoneDialog(
-        show = showConfirmation,
+        visible = showConfirmation,
         onDismissRequest = { showConfirmation = false },
+        curvedText = { openOnPhoneCurvedText(text = text, style = style) }
     )
 }
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt
index 2deb943..623a163 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt
@@ -140,24 +140,25 @@
                             Box(
                                 Modifier.size(25.dp)
                                     .drawWithContent {
-                                        drawContent()
-
-                                        val colorProgress =
-                                            scrollProgress?.let {
-                                                (it.topOffsetFraction + it.bottomOffsetFraction) /
-                                                    2f
-                                            } ?: 0f
-                                        val r = size.height / 2f
-                                        drawCircle(
-                                            rainbowColor(colorProgress),
-                                            radius = r,
-                                            center = Offset(size.width - r, r)
-                                        )
-                                        drawCircle(
-                                            rainbowColor(random.nextFloat()),
-                                            radius = r / 8,
-                                            center = Offset(size.width - r, r)
-                                        )
+                                        with(scrollProgress) {
+                                            if (isUnspecified) {
+                                                return@with
+                                            }
+                                            drawContent()
+                                            val colorProgress =
+                                                (topOffsetFraction + bottomOffsetFraction) / 2f
+                                            val r = size.height / 2f
+                                            drawCircle(
+                                                rainbowColor(colorProgress),
+                                                radius = r,
+                                                center = Offset(size.width - r, r)
+                                            )
+                                            drawCircle(
+                                                rainbowColor(random.nextFloat()),
+                                                radius = r / 8,
+                                                center = Offset(size.width - r, r)
+                                            )
+                                        }
                                     }
                                     .clickable {
                                         expandedItemKey =
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogScreenshotTest.kt
index 82d14ee..7662e62 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogScreenshotTest.kt
@@ -75,10 +75,13 @@
         rule.mainClock.autoAdvance = false
         setContentWithTheme {
             ScreenConfiguration(screenSize.size) {
+                val text = OpenOnPhoneDialogDefaults.text
+                val style = OpenOnPhoneDialogDefaults.curvedTextStyle
                 OpenOnPhoneDialog(
-                    show = true,
+                    visible = true,
                     modifier = Modifier.size(screenSize.size.dp).testTag(TEST_TAG),
                     onDismissRequest = {},
+                    curvedText = { openOnPhoneCurvedText(text = text, style = style) }
                 )
             }
         }
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogTest.kt
index 65742be..7582d69 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/OpenOnPhoneDialogTest.kt
@@ -43,10 +43,12 @@
     @Test
     fun openOnPhone_supports_testtag() {
         rule.setContentWithTheme {
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
-                show = true,
+                visible = true,
                 modifier = Modifier.testTag(TEST_TAG),
                 onDismissRequest = {},
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
             )
         }
         rule.onNodeWithTag(TEST_TAG).assertExists()
@@ -57,14 +59,16 @@
         var dismissCounter = 0
         rule.mainClock.autoAdvance = false
         rule.setContentWithTheme {
-            var showDialog by remember { mutableStateOf(true) }
+            var visible by remember { mutableStateOf(true) }
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
                 modifier = Modifier.testTag(TEST_TAG),
                 onDismissRequest = {
-                    showDialog = false
+                    visible = false
                     dismissCounter++
                 },
-                show = showDialog
+                visible = visible,
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
             )
         }
         rule.mainClock.advanceTimeBy(OpenOnPhoneDialogDefaults.DurationMillis / 2)
@@ -78,10 +82,12 @@
     @Test
     fun hides_openOnPhone_when_show_false() {
         rule.setContentWithTheme {
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
-                show = false,
+                visible = false,
                 modifier = Modifier.testTag(TEST_TAG),
                 onDismissRequest = {},
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
             )
         }
         rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
@@ -90,7 +96,14 @@
     @Test
     fun openOnPhone_displays_icon() {
         rule.setContentWithTheme {
-            OpenOnPhoneDialog(onDismissRequest = {}, show = true) { TestImage(IconTestTag) }
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
+            OpenOnPhoneDialog(
+                onDismissRequest = {},
+                visible = true,
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
+            ) {
+                TestImage(IconTestTag)
+            }
         }
         rule.onNodeWithTag(IconTestTag).assertExists()
     }
@@ -98,21 +111,23 @@
     @OptIn(ExperimentalTestApi::class)
     @Test
     fun openOnPhone_onDismissRequest_not_called_when_hidden() {
-        val show = mutableStateOf(true)
+        val visible = mutableStateOf(true)
         var dismissCounter = 0
         rule.setContentWithTheme {
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
                 modifier = Modifier.testTag(TEST_TAG),
                 onDismissRequest = { dismissCounter++ },
                 durationMillis = 1000,
-                show = show.value
+                visible = visible.value,
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
             )
         }
         rule.waitForIdle()
         // First we have to wait until animation completes and goes into idle state.
         // onDismissRequest will be called once it's finished - so dismissCounter will be 1.
         Assert.assertEquals(1, dismissCounter)
-        show.value = false
+        visible.value = false
         rule.waitUntilDoesNotExist(hasTestTag(TEST_TAG))
 
         // However, onDismissRequest should not be called when show.value becomes false and dialog
@@ -123,17 +138,19 @@
     @OptIn(ExperimentalTestApi::class)
     @Test
     fun openOnPhone_calls_onDismissRequest_on_timeout() {
-        val show = mutableStateOf(true)
+        val visible = mutableStateOf(true)
         var dismissCounter = 0
         rule.setContentWithTheme {
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
                 modifier = Modifier.testTag(TEST_TAG),
                 onDismissRequest = {
                     dismissCounter++
-                    show.value = false
+                    visible.value = false
                 },
                 durationMillis = 100,
-                show = show.value
+                visible = visible.value,
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
             )
         }
         rule.waitUntilDoesNotExist(hasTestTag(TEST_TAG))
@@ -145,7 +162,12 @@
         var dismissed = false
         rule.mainClock.autoAdvance = false
         rule.setContentWithTheme {
-            OpenOnPhoneDialog(onDismissRequest = { dismissed = true }, show = true) {}
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
+            OpenOnPhoneDialog(
+                onDismissRequest = { dismissed = true },
+                visible = true,
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
+            ) {}
         }
         // Timeout longer than default confirmation duration
         rule.mainClock.advanceTimeBy(OpenOnPhoneDialogDefaults.DurationMillis + 1000)
@@ -161,10 +183,12 @@
         var expectedProgressIndicatorColor: Color = Color.Unspecified
         var expectedProgressTrackColor: Color = Color.Unspecified
         rule.setContentWithTheme {
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
                 onDismissRequest = {},
                 modifier = Modifier.testTag(TEST_TAG),
-                show = true
+                visible = true,
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
             )
             expectedIconColor = MaterialTheme.colorScheme.primary
             expectedIconContainerColor = MaterialTheme.colorScheme.primaryContainer
@@ -199,6 +223,7 @@
         val customProgressIndicatorColor: Color = Color.Blue
         val customProgressTrackColor: Color = Color.Magenta
         rule.setContentWithTheme {
+            val style = OpenOnPhoneDialogDefaults.curvedTextStyle
             OpenOnPhoneDialog(
                 onDismissRequest = {},
                 modifier = Modifier.testTag(TEST_TAG),
@@ -209,7 +234,8 @@
                         progressIndicatorColor = customProgressIndicatorColor,
                         progressTrackColor = customProgressTrackColor
                     ),
-                show = true
+                visible = true,
+                curvedText = { openOnPhoneCurvedText(text = CurvedText, style = style) }
             )
         }
         // Advance time by half of the default confirmation duration, so that the track and
@@ -227,3 +253,4 @@
 }
 
 private const val IconTestTag = "icon"
+private const val CurvedText = "CurvedText"
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
index 0bcd8d9..9e32e46 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
@@ -59,6 +59,8 @@
 import androidx.wear.compose.foundation.CurvedTextStyle
 import androidx.wear.compose.foundation.LocalReduceMotion
 import androidx.wear.compose.foundation.padding
+import androidx.wear.compose.material3.internal.Strings.Companion.OpenOnPhoneContentDescriptionIcon
+import androidx.wear.compose.material3.internal.getString
 import androidx.wear.compose.material3.tokens.ColorSchemeKeyTokens
 import androidx.wear.compose.material3.tokens.MotionTokens.DurationLong2
 import androidx.wear.compose.material3.tokens.MotionTokens.DurationShort3
@@ -72,7 +74,7 @@
  *
  * The dialog will be showing a message to the user for [durationMillis]. After a specified timeout,
  * the [onDismissRequest] callback will be invoked, where it's up to the caller to handle the
- * dismissal. To hide the dialog, [show] parameter should be set to false.
+ * dismissal. To hide the dialog, [visible] parameter should be set to false.
  *
  * This dialog is typically used to indicate that an action has been initiated and will continue on
  * the user's phone. Once this dialog is displayed, it's developer responsibility to establish the
@@ -81,30 +83,32 @@
  * Example of an [OpenOnPhoneDialog] usage:
  *
  * @sample androidx.wear.compose.material3.samples.OpenOnPhoneDialogSample
- * @param show A boolean indicating whether the dialog should be displayed.
+ * @param visible A boolean indicating whether the dialog should be displayed.
  * @param onDismissRequest A lambda function to be called when the dialog is dismissed - either by
  *   swiping right or when the [durationMillis] has passed.
- * @param modifier Modifier to be applied to the dialog content.
  * @param curvedText A slot for displaying curved text content which will be shown along the bottom
- *   edge of the dialog. Defaults to a localized open on phone message.
+ *   edge of the dialog. We recommend using [openOnPhoneCurvedText] for this parameter, which will
+ *   give the default sweep angle and padding, and [OpenOnPhoneDialogDefaults.curvedTextStyle] as
+ *   the style.
+ * @param modifier Modifier to be applied to the dialog content.
  * @param colors [OpenOnPhoneDialogColors] that will be used to resolve the colors used for this
  *   [OpenOnPhoneDialog].
  * @param properties An optional [DialogProperties] object for configuring the dialog's behavior.
- * @param durationMillis The duration in milliseconds for which the dialog is displayed. Defaults to
- *   [OpenOnPhoneDialogDefaults.DurationMillis].
+ * @param durationMillis The duration in milliseconds for which the dialog is displayed. This value
+ *   will be adjusted by the accessibility manager according to the content displayed.
  * @param content A slot for displaying an icon inside the open on phone dialog, which can be
- *   animated. Defaults to [OpenOnPhoneDialogDefaults.OpenOnPhoneIcon].
+ *   animated. Defaults to [OpenOnPhoneDialogDefaults.Icon].
  */
 @Composable
 public fun OpenOnPhoneDialog(
-    show: Boolean,
+    visible: Boolean,
     onDismissRequest: () -> Unit,
+    curvedText: (CurvedScope.() -> Unit)?,
     modifier: Modifier = Modifier,
-    curvedText: (CurvedScope.() -> Unit)? = OpenOnPhoneDialogDefaults.curvedText(),
     colors: OpenOnPhoneDialogColors = OpenOnPhoneDialogDefaults.colors(),
     properties: DialogProperties = DialogProperties(),
     durationMillis: Long = OpenOnPhoneDialogDefaults.DurationMillis,
-    content: @Composable BoxScope.() -> Unit = OpenOnPhoneDialogDefaults.OpenOnPhoneIcon,
+    content: @Composable () -> Unit = { OpenOnPhoneDialogDefaults.Icon() },
 ) {
     val a11yFullDurationMillis =
         LocalAccessibilityManager.current?.calculateRecommendedTimeoutMillis(
@@ -114,109 +118,187 @@
             containsControls = false,
         ) ?: durationMillis
 
-    LaunchedEffect(show, a11yFullDurationMillis) {
-        if (show) {
+    LaunchedEffect(visible, a11yFullDurationMillis) {
+        if (visible) {
             delay(a11yFullDurationMillis)
             onDismissRequest()
         }
     }
     Dialog(
-        visible = show,
+        visible = visible,
         modifier = modifier,
         onDismissRequest = onDismissRequest,
         properties = properties,
     ) {
-        var progress by remember { mutableFloatStateOf(0f) }
-        val progressAnimatable = remember { Animatable(0f) }
-        val alphaAnimatable = remember { Animatable(0f) }
+        OpenOnPhoneDialogContent(
+            curvedText = curvedText,
+            durationMillis = a11yFullDurationMillis,
+            colors = colors,
+            content = content
+        )
+    }
+}
 
-        var finalAnimation by remember { mutableStateOf(false) }
+/**
+ * This composable provides the content for an [OpenOnPhoneDialog] that displays an animated icon
+ * with curved text at the bottom.
+ *
+ * Prefer using [OpenOnPhoneDialog] directly, which provides built-in animations when showing/hiding
+ * the dialog. This composable may be used to provide the content for an openOnPhone dialog if
+ * custom show/hide animations are required.
+ *
+ * Example of an [OpenOnPhoneDialog] usage:
+ *
+ * @sample androidx.wear.compose.material3.samples.OpenOnPhoneDialogSample
+ * @param curvedText A slot for displaying curved text content which will be shown along the bottom
+ *   edge of the dialog. We recommend using [openOnPhoneCurvedText] for this parameter, which will
+ *   give the default sweep angle and padding, and [OpenOnPhoneDialogDefaults.curvedTextStyle] as
+ *   the style.
+ * @param durationMillis The duration in milliseconds for which the progress indicator inside of
+ *   this content is animated. This value should be previously adjusted by the accessibility manager
+ *   according to the content displayed. See [OpenOnPhoneDialog] implementation for more details.
+ * @param modifier Modifier to be applied to the openOnPhone content.
+ * @param colors [OpenOnPhoneDialogColors] that will be used to resolve the colors used for this
+ *   [OpenOnPhoneDialog].
+ * @param content A slot for displaying an icon inside the open on phone dialog, which can be
+ *   animated. Defaults to [OpenOnPhoneDialogDefaults.Icon].
+ */
+@Composable
+public fun OpenOnPhoneDialogContent(
+    curvedText: (CurvedScope.() -> Unit)?,
+    durationMillis: Long,
+    modifier: Modifier = Modifier,
+    colors: OpenOnPhoneDialogColors = OpenOnPhoneDialogDefaults.colors(),
+    content: @Composable () -> Unit
+): Unit {
+    var progress by remember { mutableFloatStateOf(0f) }
+    val progressAnimatable = remember { Animatable(0f) }
+    val alphaAnimatable = remember { Animatable(0f) }
 
-        val finalAnimationDuration = DurationLong2
-        val progressDuration = a11yFullDurationMillis - finalAnimationDuration
+    var finalAnimation by remember { mutableStateOf(false) }
 
-        val alphaAnimationSpec = MaterialTheme.motionScheme.fastEffectsSpec<Float>()
-        val reduceMotionEnabled = LocalReduceMotion.current
+    val finalAnimationDuration = DurationLong2
+    val progressDuration = durationMillis - finalAnimationDuration
 
-        LaunchedEffect(a11yFullDurationMillis) {
-            launch {
-                animatedDelay(DurationShort3.toLong(), reduceMotionEnabled)
-                alphaAnimatable.animateTo(1f, alphaAnimationSpec)
-            }
-            launch {
-                if (!reduceMotionEnabled) {
-                    progressAnimatable.animateTo(
-                        targetValue = 1f,
-                        animationSpec =
-                            tween(durationMillis = progressDuration.toInt(), easing = LinearEasing),
-                    ) {
-                        progress = value
-                    }
-                    finalAnimation = true
+    val alphaAnimationSpec = MaterialTheme.motionScheme.fastEffectsSpec<Float>()
+    val reduceMotionEnabled = LocalReduceMotion.current
+
+    LaunchedEffect(durationMillis) {
+        launch {
+            animatedDelay(DurationShort3.toLong(), reduceMotionEnabled)
+            alphaAnimatable.animateTo(1f, alphaAnimationSpec)
+        }
+        launch {
+            if (!reduceMotionEnabled) {
+                progressAnimatable.animateTo(
+                    targetValue = 1f,
+                    animationSpec =
+                        tween(durationMillis = progressDuration.toInt(), easing = LinearEasing),
+                ) {
+                    progress = value
                 }
+                finalAnimation = true
             }
         }
+    }
 
-        val colorReversalAnimationSpec = MaterialTheme.motionScheme.defaultEffectsSpec<Color>()
-        val sizeAnimationSpec = MaterialTheme.motionScheme.defaultSpatialSpec<Float>()
-        val progressAlphaAnimationSpec = MaterialTheme.motionScheme.defaultEffectsSpec<Float>()
+    val colorReversalAnimationSpec = MaterialTheme.motionScheme.defaultEffectsSpec<Color>()
+    val sizeAnimationSpec = MaterialTheme.motionScheme.defaultSpatialSpec<Float>()
+    val progressAlphaAnimationSpec = MaterialTheme.motionScheme.defaultEffectsSpec<Float>()
 
-        val sizeAnimationFraction =
-            animateFloatAsState(if (finalAnimation) 0f else 1f, sizeAnimationSpec)
-        val progressAlphaAnimationFraction =
-            animateFloatAsState(if (finalAnimation) 0f else 1f, progressAlphaAnimationSpec)
-        val iconColor =
-            animateColorAsState(
-                if (finalAnimation) colors.iconContainerColor else colors.iconColor,
-                colorReversalAnimationSpec
-            )
-        val iconContainerColor =
-            animateColorAsState(
-                if (finalAnimation) colors.iconColor else colors.iconContainerColor,
-                colorReversalAnimationSpec
-            )
+    val sizeAnimationFraction =
+        animateFloatAsState(if (finalAnimation) 0f else 1f, sizeAnimationSpec)
+    val progressAlphaAnimationFraction =
+        animateFloatAsState(if (finalAnimation) 0f else 1f, progressAlphaAnimationSpec)
+    val iconColor =
+        animateColorAsState(
+            if (finalAnimation) colors.iconContainerColor else colors.iconColor,
+            colorReversalAnimationSpec
+        )
+    val iconContainerColor =
+        animateColorAsState(
+            if (finalAnimation) colors.iconColor else colors.iconContainerColor,
+            colorReversalAnimationSpec
+        )
 
-        Box(modifier = Modifier.fillMaxSize()) {
-            val topPadding = screenHeightDp() * HeightPaddingFraction
-            val size = screenWidthDp() * SizeFraction
-            Box(
+    Box(modifier = modifier.fillMaxSize()) {
+        val topPadding = screenHeightDp() * HeightPaddingFraction
+        val size = screenWidthDp() * SizeFraction
+        Box(
+            modifier =
                 Modifier.padding(top = topPadding.dp).size(size.dp).align(Alignment.TopCenter),
-            ) {
-                iconContainer(
-                    iconContainerColor = iconContainerColor.value,
-                    progressIndicatorColors =
-                        ProgressIndicatorDefaults.colors(
-                            SolidColor(colors.progressIndicatorColor),
-                            SolidColor(colors.progressTrackColor)
-                        ),
-                    sizeAnimationFraction = sizeAnimationFraction,
-                    progressAlphaAnimationFraction = progressAlphaAnimationFraction,
-                    progress = { progress }
-                )()
-                CompositionLocalProvider(LocalContentColor provides iconColor.value) { content() }
-            }
-            CompositionLocalProvider(LocalContentColor provides colors.textColor) {
-                curvedText?.let {
-                    CurvedLayout(
-                        modifier = Modifier.graphicsLayer { alpha = alphaAnimatable.value },
-                        anchor = 90f,
-                        contentBuilder = curvedText
-                    )
-                }
+            contentAlignment = Alignment.Center
+        ) {
+            iconContainer(
+                iconContainerColor = iconContainerColor.value,
+                progressIndicatorColors =
+                    ProgressIndicatorDefaults.colors(
+                        SolidColor(colors.progressIndicatorColor),
+                        SolidColor(colors.progressTrackColor)
+                    ),
+                sizeAnimationFraction = sizeAnimationFraction,
+                progressAlphaAnimationFraction = progressAlphaAnimationFraction,
+                progress = { progress }
+            )()
+            CompositionLocalProvider(LocalContentColor provides iconColor.value, content)
+        }
+        CompositionLocalProvider(LocalContentColor provides colors.textColor) {
+            curvedText?.let {
+                CurvedLayout(
+                    modifier = Modifier.graphicsLayer { alpha = alphaAnimatable.value },
+                    anchor = 90f,
+                    angularDirection = CurvedDirection.Angular.Reversed,
+                    contentBuilder = curvedText
+                )
             }
         }
     }
 }
 
+/**
+ * A customized variation of [androidx.wear.compose.material3.curvedText] that displays text along a
+ * curved path. This variation adopts suitable sweep angle and padding for use in
+ * [OpenOnPhoneDialog].
+ *
+ * @param text The text to display.
+ * @param style The style to apply to the text. It is recommended to use
+ *   [OpenOnPhoneDialogDefaults.curvedTextStyle] for curved text in [OpenOnPhoneDialog].
+ */
+public fun CurvedScope.openOnPhoneCurvedText(
+    text: String,
+    style: CurvedTextStyle,
+): Unit =
+    curvedText(
+        text = text,
+        style = style,
+        maxSweepAngle = CurvedTextDefaults.StaticContentMaxSweepAngle,
+        modifier = CurvedModifier.padding(PaddingDefaults.edgePadding)
+    )
+
 /** Contains the default values used by [OpenOnPhoneDialog]. */
 public object OpenOnPhoneDialogDefaults {
 
+    /** The default style for curved text content. */
+    public val curvedTextStyle: CurvedTextStyle
+        @Composable get() = CurvedTextStyle(MaterialTheme.typography.titleLarge)
+
+    /** The default message for an [OpenOnPhoneDialog]. */
+    public val text: String
+        @Composable get() = LocalContext.current.getString(R.string.wear_m3c_open_on_phone)
+
     /**
      * A default composable used in [OpenOnPhoneDialog] that displays an open on phone icon with an
      * animation.
+     *
+     * @param modifier Modifier to be applied to the icon.
+     * @param contentDescription The content description for the icon.
      */
     @OptIn(ExperimentalAnimationGraphicsApi::class)
-    public val OpenOnPhoneIcon: @Composable BoxScope.() -> Unit = {
+    @Composable
+    public fun Icon(
+        modifier: Modifier = Modifier,
+        contentDescription: String = iconContentDescription
+    ) {
         val animation =
             AnimatedImageVector.animatedVectorResource(R.drawable.wear_m3c_open_on_phone_animation)
         var atEnd by remember { mutableStateOf(false) }
@@ -228,31 +310,14 @@
         }
         Icon(
             painter = rememberAnimatedVectorPainter(animation, atEnd),
-            contentDescription = null,
-            modifier = Modifier.size(IconSize).align(Alignment.Center),
+            contentDescription = contentDescription,
+            modifier = modifier.size(IconSize)
         )
     }
 
-    /**
-     * A default composable that displays text along a curved path, used in [OpenOnPhoneDialog].
-     *
-     * @param text The text to display. Defaults to an open on phone message.
-     * @param style The style to apply to the text. Defaults to
-     *   CurvedTextStyle(MaterialTheme.typography.titleLarge).
-     */
-    @Composable
-    public fun curvedText(
-        text: String = LocalContext.current.resources.getString(R.string.wear_m3c_open_on_phone),
-        style: CurvedTextStyle = CurvedTextStyle(MaterialTheme.typography.titleLarge)
-    ): CurvedScope.() -> Unit = {
-        curvedText(
-            text = text,
-            style = style,
-            maxSweepAngle = CurvedTextDefaults.StaticContentMaxSweepAngle,
-            modifier = CurvedModifier.padding(PaddingDefaults.edgePadding),
-            angularDirection = CurvedDirection.Angular.Reversed
-        )
-    }
+    /** The default content description for the open on phone icon */
+    public val iconContentDescription: String
+        @Composable get() = getString(OpenOnPhoneContentDescriptionIcon)
 
     /**
      * Creates a [OpenOnPhoneDialogColors] that represents the default colors used in
@@ -393,6 +458,7 @@
                 clip = true
             }
             .background(iconContainerColor)
+            .align(Alignment.Center)
     )
 
     CircularProgressIndicatorStatic(
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
index a618cff..7206e82 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
@@ -91,6 +91,9 @@
 
         inline val AlertDialogContentDescriptionDismissButton
             get() = Strings(R.string.wear_m3c_alert_dialog_content_description_dismiss_button)
+
+        inline val OpenOnPhoneContentDescriptionIcon
+            get() = Strings(R.string.wear_m3c_open_on_phone_icon_content_description)
     }
 }
 
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/BackgroundPainter.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/BackgroundPainter.kt
index bdd2c7a..558de8f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/BackgroundPainter.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/BackgroundPainter.kt
@@ -35,41 +35,43 @@
     private val shape: Shape,
     private val border: BorderStroke?,
     private val backgroundPainter: Painter,
-    private val progress: DrawScope.() -> TransformingLazyColumnItemScrollProgress?
+    private val progress: DrawScope.() -> TransformingLazyColumnItemScrollProgress
 ) : Painter() {
     override val intrinsicSize: Size
         get() = Size.Unspecified
 
     override fun DrawScope.onDraw() {
         with(behavior) {
-            progress()?.let {
-                val contentWidth =
-                    (1f - 2 * (1f - it.backgroundXOffsetFraction)) * size.width * it.scale
-                val xOffset = (size.width - contentWidth) / 2f
+            progress()
+                .takeIf { it != TransformingLazyColumnItemScrollProgress.Unspecified }
+                ?.let {
+                    val contentWidth =
+                        (1f - 2 * (1f - it.backgroundXOffsetFraction)) * size.width * it.scale
+                    val xOffset = (size.width - contentWidth) / 2f
 
-                translate(xOffset, 0f) {
-                    val placementHeight = it.placementHeight(size.height)
-                    val shapeOutline =
-                        shape.createOutline(
-                            Size(contentWidth, placementHeight),
-                            layoutDirection,
-                            this@onDraw
-                        )
-
-                    // TODO: b/376693576 - cache the path.
-                    clipPath(Path().apply { addOutline(shapeOutline) }) {
-                        if (border != null) {
-                            drawOutline(
-                                outline = shapeOutline,
-                                brush = border.brush,
-                                alpha = it.backgroundAlpha,
-                                style = Stroke(border.width.toPx())
+                    translate(xOffset, 0f) {
+                        val placementHeight = it.placementHeight(size.height)
+                        val shapeOutline =
+                            shape.createOutline(
+                                Size(contentWidth, placementHeight),
+                                layoutDirection,
+                                this@onDraw
                             )
+
+                        // TODO: b/376693576 - cache the path.
+                        clipPath(Path().apply { addOutline(shapeOutline) }) {
+                            if (border != null) {
+                                drawOutline(
+                                    outline = shapeOutline,
+                                    brush = border.brush,
+                                    alpha = it.backgroundAlpha,
+                                    style = Stroke(border.width.toPx())
+                                )
+                            }
+                            with(backgroundPainter) { draw(Size(contentWidth, placementHeight)) }
                         }
-                        with(backgroundPainter) { draw(Size(contentWidth, placementHeight)) }
                     }
                 }
-            }
         }
     }
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ContentTransformation.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ContentTransformation.kt
index 1c1835b..911081f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ContentTransformation.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ContentTransformation.kt
@@ -29,39 +29,41 @@
 
 internal fun GraphicsLayerScope.contentTransformation(
     behavior: TransformingLazyColumnScrollTransformBehavior,
-    scrollProgress: () -> TransformingLazyColumnItemScrollProgress?
+    scrollProgress: () -> TransformingLazyColumnItemScrollProgress
 ) =
     with(behavior) {
-        scrollProgress()?.let {
-            compositingStrategy = CompositingStrategy.Offscreen
-            clip = true
-            shape =
-                object : Shape {
-                    override fun createOutline(
-                        size: Size,
-                        layoutDirection: LayoutDirection,
-                        density: Density
-                    ): Outline =
-                        Outline.Rounded(
-                            RoundRect(
-                                rect =
-                                    Rect(
-                                        left = 0f,
-                                        top = 0f,
-                                        right =
-                                            size.width -
-                                                2f * size.width * it.contentXOffsetFraction,
-                                        bottom = it.morphedHeight(size.height)
-                                    ),
+        scrollProgress()
+            .takeIf { it != TransformingLazyColumnItemScrollProgress.Unspecified }
+            ?.let {
+                compositingStrategy = CompositingStrategy.Offscreen
+                clip = true
+                shape =
+                    object : Shape {
+                        override fun createOutline(
+                            size: Size,
+                            layoutDirection: LayoutDirection,
+                            density: Density
+                        ): Outline =
+                            Outline.Rounded(
+                                RoundRect(
+                                    rect =
+                                        Rect(
+                                            left = 0f,
+                                            top = 0f,
+                                            right =
+                                                size.width -
+                                                    2f * size.width * it.contentXOffsetFraction,
+                                            bottom = it.morphedHeight(size.height)
+                                        ),
+                                )
                             )
-                        )
-                }
-            translationX = size.width * it.contentXOffsetFraction * it.scale
-            translationY = -1f * size.height * (1f - it.scale) / 2f
-            alpha = it.contentAlpha
-            scaleX = it.scale
-            scaleY = it.scale
-        }
+                    }
+                translationX = size.width * it.contentXOffsetFraction * it.scale
+                translationY = -1f * size.height * (1f - it.scale) / 2f
+                alpha = it.contentAlpha
+                scaleX = it.scale
+                scaleY = it.scale
+            }
     }
 
 internal fun GraphicsLayerScope.contentTransformation(
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ScrollTransform.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ScrollTransform.kt
index 75033c9..052b14e 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ScrollTransform.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/lazy/ScrollTransform.kt
@@ -382,10 +382,10 @@
 
 /** Uses a TransformationSpec to convert a scrollProgress into a transitionProgress. */
 private fun transformProgress(
-    scrollProgress: TransformingLazyColumnItemScrollProgress?,
+    scrollProgress: TransformingLazyColumnItemScrollProgress,
     spec: TransformationSpec
 ): TransitionAreaProgress =
-    if (scrollProgress == null) {
+    if (scrollProgress == TransformingLazyColumnItemScrollProgress.Unspecified) {
         TransitionAreaProgress.None
     } else {
         // Size of the item, relative to the screen
diff --git a/wear/compose/compose-material3/src/main/res/values/strings.xml b/wear/compose/compose-material3/src/main/res/values/strings.xml
index c6bf584..3cfac74 100644
--- a/wear/compose/compose-material3/src/main/res/values/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values/strings.xml
@@ -41,4 +41,5 @@
     <string description="Content description of the Confirm icon of AlertDialog components. It lets the user confirm the action of the dialog. [CHAR_LIMIT=NONE]" name="wear_m3c_alert_dialog_content_description_confirm_button">Confirm</string>
     <string description="Content description of the Dismiss icon of AlertDialog components. It lets the user dismiss the dialog. [CHAR_LIMIT=NONE]" name="wear_m3c_alert_dialog_content_description_dismiss_button">Dismiss</string>
     <string description="A message which is used to indicate than the user should continue their action on their phone, used in OpenOnPhone component [CHAR_LIMIT=12]" name="wear_m3c_open_on_phone">Open on phone</string>
+    <string description="Content description of the icon of OpenOnPhone component. [CHAR_LIMIT=NONE]" name="wear_m3c_open_on_phone_icon_content_description">Open on phone icon</string>
 </resources>
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 504eddc..9243bf3 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -26,8 +26,8 @@
     defaultConfig {
         applicationId = "androidx.wear.compose.integration.demos"
         minSdk = 25
-        versionCode = 64
-        versionName = "1.64"
+        versionCode = 65
+        versionName = "1.65"
     }
 
     buildTypes {
diff --git a/xr/compose/integration-tests/layout/spatialcomposeapp/src/main/java/androidx/xr/compose/integration/layout/spatialcomposeapp/SpatialComposeAppActivity.kt b/xr/compose/integration-tests/layout/spatialcomposeapp/src/main/java/androidx/xr/compose/integration/layout/spatialcomposeapp/SpatialComposeAppActivity.kt
index c5ef07e..d8c1c8e 100644
--- a/xr/compose/integration-tests/layout/spatialcomposeapp/src/main/java/androidx/xr/compose/integration/layout/spatialcomposeapp/SpatialComposeAppActivity.kt
+++ b/xr/compose/integration-tests/layout/spatialcomposeapp/src/main/java/androidx/xr/compose/integration/layout/spatialcomposeapp/SpatialComposeAppActivity.kt
@@ -90,9 +90,9 @@
 import androidx.xr.runtime.math.Vector3
 import androidx.xr.scenecore.GltfModel
 import androidx.xr.scenecore.Session
+import java.time.Clock
 import kotlin.math.cos
 import kotlin.math.sin
-import kotlin.time.TimeSource
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.guava.await
 import kotlinx.coroutines.launch
@@ -305,18 +305,14 @@
 
                 lifecycleScope.launch {
                     val pi = 3.14159F
-                    @OptIn(kotlin.time.ExperimentalTime::class)
-                    val timeSource = TimeSource.Monotonic
-                    @OptIn(kotlin.time.ExperimentalTime::class) val startTime = timeSource.markNow()
+                    val timeSource = Clock.systemUTC()
+                    val startTime = timeSource.millis()
                     val rotateTimeMs = 10000F
 
                     while (true) {
                         delay(16L)
-                        @OptIn(kotlin.time.ExperimentalTime::class)
-                        val angle =
-                            (2 * pi) * ((timeSource.markNow() - startTime).inWholeMilliseconds) /
-                                rotateTimeMs
-
+                        val elapsedMs = timeSource.millis() - startTime
+                        val angle = (2 * pi) * (elapsedMs / rotateTimeMs)
                         val normalized = Vector3(1.0f, 1.0f, 1.0f).toNormalized()
 
                         val qX = normalized.x * sin(angle / 2)